Compare commits

...

371 Commits

Author SHA1 Message Date
Faruk AYDIN
b9d89b040f Release v0.7.0 2023-05-19 12:29:29 +02:00
Faruk AYDIN
41421b849a chore: Update version to 0.7.0 in Dockerfiles 2023-05-19 12:28:59 +02:00
Rıdvan Akca
324375da93 fix(shared-drive): show shared drive items 2023-05-19 07:35:49 +02:00
Ali BARIN
536446faf6 fix(twilio/receive-sms): use phonenumber sid in removing webhook 2023-05-17 12:57:02 +02:00
Rıdvan Akca
d026ac09f3 feat(google-sheets): add new worksheets trigger 2023-05-16 23:42:23 +02:00
Ali BARIN
88c93ac992 fix(dynamic-data): correct parameters 2023-05-16 17:59:04 +02:00
Ali BARIN
d540322d8b refactor(webhook): respond with 204 instead of 200 2023-05-16 16:40:12 +02:00
Ali BARIN
ad4db5e936 feat(twilio/send-sms): use dynamic phone numbers 2023-05-16 16:40:12 +02:00
Ali BARIN
25cb4d90f3 refactor(twilio/receive-sms): convert to webhook 2023-05-16 16:40:12 +02:00
Ömer Faruk Aydın
6c14a353ef Merge pull request #1093 from automatisch/openai-chat-prompt
feat(openai): add chat prompt
2023-05-16 12:46:41 +02:00
Ali BARIN
74d7d1aa98 feat(openai): add chat prompt 2023-05-15 20:41:25 +00:00
Ali BARIN
43b0d9ed29 fix: refetch step executions upon deleting and testing steps 2023-05-15 16:24:26 +02:00
Ali BARIN
3572e6f65a fix: send parameters to dynamic data query 2023-05-15 16:24:17 +02:00
Ali BARIN
d23d5d2da0 feat(slack): send direct message 2023-05-15 16:24:17 +02:00
Faruk AYDIN
183b9b0d88 feat: Add enable ssl field to PostgreSQL connection 2023-05-15 16:23:43 +02:00
Faruk AYDIN
7a1af268ae chore: Remove empty line from PostgreSQL index file 2023-05-15 16:23:43 +02:00
Ali BARIN
f879b3c5b0 refactor(postgresql): rename pgClient with client 2023-05-15 16:23:43 +02:00
Ali BARIN
40be72cf65 feat(postgresql/delete): add interactive where clause entries 2023-05-15 16:23:43 +02:00
Ali BARIN
a8886571d1 feat(postgresql/update): add interactive where clause 2023-05-15 16:23:43 +02:00
Ali BARIN
1fcd51ea26 refactor(postgresql/insert): use withSchema 2023-05-15 16:23:43 +02:00
Ali BARIN
89752138be refactor(postgresql): use bindings to set run-time params 2023-05-15 16:23:43 +02:00
Ali BARIN
f29ccace2a chore(postgresql): rename app folder and add icon 2023-05-15 16:23:43 +02:00
Shehab Ghazy
0c8343e76f feat(postgresql): add auth and primitive actions 2023-05-15 16:23:43 +02:00
Ali BARIN
9776c9f5a4 feat: add duplicate flow functionality 2023-05-13 14:44:21 +02:00
Ali BARIN
a5dbac9817 feat(ExecutionStep): show execution date 2023-05-13 13:20:05 +02:00
Ali BARIN
bad5e0b855 fix(queries/get-execution): serve soft deleteds 2023-05-13 13:19:53 +02:00
Ali BARIN
8e4ca55560 feat(ControlledAutocomplete): filter by value too 2023-05-13 13:19:42 +02:00
Ali BARIN
f52afc1fe0 chore: serve graphql explorer only on development 2023-05-13 13:19:35 +02:00
Ali BARIN
815e64302e fix(Editor): don't unregister step parameters 2023-05-13 13:19:28 +02:00
Faruk AYDIN
07b2b18a4e fix: Run remove cancelled subscriptions only in the cloud 2023-05-12 13:30:06 +02:00
Ömer Faruk Aydın
69eca33de7 Merge pull request #1080 from automatisch/docs-postgres-ssl
docs: Add POSTGRES_ENABLE_SSL env variable to configuration
2023-05-11 14:50:32 +02:00
Faruk AYDIN
ec76a480d0 docs: Add POSTGRES_ENABLE_SSL env variable to configuration 2023-05-11 13:58:02 +02:00
Ali BARIN
a8823c3ed0 fix(filters/continue): cover multiple conditions 2023-05-09 18:28:51 +02:00
Ali BARIN
1f1b3a341c refactor: make sentry cloud agnostic 2023-05-09 13:17:33 +02:00
Ali BARIN
8c164a3852 fix(http-request): suppress failure upon size check 2023-05-08 14:45:41 +02:00
Ali BARIN
dcf526d810 feat(http-request): convert non-text data to base64 2023-05-08 12:52:09 +02:00
Ali BARIN
2fc6d680a0 fix: incorporate spaces in variables 2023-05-08 12:52:09 +02:00
Ali BARIN
f414972f33 Merge pull request #1074 from automatisch/executions-updated-at
feat: sort executions by updated at
2023-05-08 12:49:15 +02:00
Ali BARIN
69d192d989 feat: sort executions by updated at 2023-05-02 09:30:41 +00:00
Ömer Faruk Aydın
6c8769e598 Merge pull request #1071 from automatisch/paddle-plans
feat: Introduce new plans for the cloud
2023-04-26 13:21:18 +02:00
Faruk AYDIN
c12703422c feat: Introduce new plans for the cloud 2023-04-26 12:13:39 +02:00
Ömer Faruk Aydın
c0171e1cd1 Merge pull request #1070 from automatisch/dockerfile.cloud
chore: add dockerfile for cloud
2023-04-26 11:20:00 +02:00
Ali BARIN
920a983146 chore: add dockerfile for cloud 2023-04-25 20:43:48 +00:00
Ömer Faruk Aydın
7ec86bfef1 Merge pull request #1069 from automatisch/release/0.6.1
Release v0.6.1
2023-04-25 18:17:04 +02:00
Faruk AYDIN
d8bc318688 Release v0.6.1 2023-04-25 17:15:20 +02:00
Faruk AYDIN
600ea1848f chore: Update version to 0.6.1 in Dockerfiles 2023-04-25 17:14:43 +02:00
Ömer Faruk Aydın
a3ce9c7662 Merge pull request #1068 from automatisch/copy-hbs
fix: Include email templates for the build
2023-04-25 17:10:50 +02:00
Faruk AYDIN
44ce7577c6 fix: Include email templates for the build 2023-04-25 17:07:46 +02:00
Ömer Faruk Aydın
8c3e42f7eb Merge pull request #1067 from automatisch/company-name
chore: Use company name for the enterprise license
2023-04-24 18:20:34 +02:00
Faruk AYDIN
b8887c506c chore: Use company name for the enterprise license 2023-04-24 16:38:54 +02:00
Ömer Faruk Aydın
6c4228b7b8 Merge pull request #1063 from automatisch/docs/encryption-key-generation
docs: Explain how to create random keys for encryption and webhook secret keys
2023-04-20 17:07:38 +02:00
Faruk AYDIN
9b1da98386 docs: Explain how to create random keys for encryption and webhook secret keys 2023-04-19 18:44:19 +02:00
Ömer Faruk Aydın
1615169a3d Merge pull request #1060 from automatisch/release/0.6.0
Release v0.6.0
2023-04-17 19:05:25 +02:00
Faruk AYDIN
df83aa4d15 chore: Update version with 0.6.0 2023-04-17 18:37:24 +02:00
Faruk AYDIN
142b96beb0 Release v0.6.0 2023-04-17 18:35:16 +02:00
Shehab Ghazy
18d07dd3b9 feat: Add postgres schema environment variable (#1047)
feat: Add postgres schema environment variable

---------

Co-authored-by: Faruk AYDIN <omerfaruk26@gmail.com>
2023-04-17 14:56:12 +02:00
Ali BARIN
270039d211 Merge pull request #1058 from automatisch/remove-payment-plans
chore: remove payment plan model and related functionality
2023-04-16 20:32:01 +02:00
Ali BARIN
fcf3a480be chore: remove UsageAlert 2023-04-16 14:22:30 +00:00
Ali BARIN
80ee974973 chore: remove PaymentPlan model 2023-04-16 14:05:07 +00:00
Ömer Faruk Aydın
48c6a38205 Merge pull request #1057 from automatisch/AUT-98
feat: add subscription cancelled alert
2023-04-13 18:30:52 +02:00
Faruk AYDIN
c9536e58cb feat: Cover active but cancelled subscriptions 2023-04-13 18:27:57 +02:00
Ali BARIN
a16d783302 Merge pull request #1056 from automatisch/fix-within-limits-method
fix(user): correct withinLimits instance method
2023-04-12 22:17:11 +02:00
Ali BARIN
b837aecb27 feat: add subscription cancelled alert 2023-04-12 20:08:45 +00:00
Ali BARIN
532405048f fix(user): correct withinLimits instance method 2023-04-12 19:40:00 +00:00
Ali BARIN
6cd0427ff8 Merge pull request #1055 from automatisch/subscription-cancelled
feat: add subscription cancelled flow
2023-04-12 21:33:22 +02:00
Faruk AYDIN
d2163f180e feat: Add getSubscriptionStatus graphQL query 2023-04-12 21:29:36 +02:00
Faruk AYDIN
d1344457dd feat: Auto remove cancelled subscriptions 2023-04-11 23:32:34 +02:00
Faruk AYDIN
596be24d92 feat: Add cancellation effective date to subscriptions 2023-04-11 22:45:12 +02:00
Ömer Faruk Aydın
4e182b49f9 Merge pull request #1053 from automatisch/is-paused
fix: Return isPaused according to isAllowedToRunFlows method
2023-04-11 17:43:26 +02:00
Faruk AYDIN
51ac37f1de fix: Return isPaused according to isAllowedToRunFlows method 2023-04-11 17:11:38 +02:00
Ömer Faruk Aydın
b578e88d46 Merge pull request #1050 from automatisch/add-virtual-flow-status
feat(flow): add virtual paused/published/draft status
2023-04-11 17:04:32 +02:00
Faruk AYDIN
9cb4607f69 refactor: User permission and limits to run flows 2023-04-11 16:59:59 +02:00
Ömer Faruk Aydın
2c4d602028 Merge pull request #1051 from automatisch/aut-97
feat: show completed checkout alert
2023-04-10 13:13:23 +02:00
Ali BARIN
a0feb7f309 feat: show completed checkout alert 2023-04-09 20:31:51 +00:00
Ali BARIN
77e29050c8 feat(flow): add virtual paused/published/draft status 2023-04-09 16:45:41 +00:00
Ömer Faruk Aydın
df55f746ca Merge pull request #1048 from automatisch/aut-92
feat: add trial status badge in appbar
2023-04-09 15:30:04 +02:00
Ali BARIN
2739d2297f feat: finalize TrialOverAlert on billing page 2023-04-09 14:11:22 +02:00
Faruk AYDIN
dc734b04d8 feat: Implement draft version of free trial over info box 2023-04-09 14:11:22 +02:00
Ali BARIN
d4c542168c feat: add trial status badge in appbar 2023-04-09 09:57:50 +00:00
Ömer Faruk Aydın
94a3b66130 Merge pull request #1040 from automatisch/usage-data
feat: add subscription update and cancel logics
2023-04-08 00:34:28 +03:00
Faruk AYDIN
c8baf9b0d7 feat: Check if user has active subscription or not for execution 2023-04-07 23:09:24 +02:00
Faruk AYDIN
c4dc0509c2 feat: Implement getTrialStatus graphQL query 2023-04-07 22:55:18 +02:00
Faruk AYDIN
3bfc428dfe feat: Add inTrial getter method to User model 2023-04-07 22:35:56 +02:00
Ömer Faruk Aydın
026b7da6f8 Merge pull request #1046 from automatisch/move-paddle-provider-deeper-in-app-tree
refactor(paddle): move script injection closer to checkout
2023-04-07 23:07:02 +03:00
Ali BARIN
58352b9f33 refactor(paddle): move script injection closer to checkout 2023-04-07 17:09:10 +00:00
Ömer Faruk Aydın
59faf593be Merge pull request #1045 from automatisch/expose-worker-errors
fix: Expose worker errors with stack trace
2023-04-07 19:23:56 +03:00
Faruk AYDIN
4aa41773c4 fix: Expose worker errors with stack trace 2023-04-07 18:16:08 +02:00
Ömer Faruk Aydın
20c5d6aee1 Merge pull request #1044 from automatisch/strava
feat(strava): add action to create totals and stats report
2023-04-07 16:24:21 +03:00
Ali BARIN
d503dbc103 docs(strava): describe connection and actions 2023-04-07 14:16:30 +02:00
Ali BARIN
53624a6379 feat(strava): add action to create totals and stats report 2023-04-07 14:16:30 +02:00
Ali BARIN
5b881db19f feat(strava): refresh token when expired 2023-04-07 14:16:30 +02:00
Ali BARIN
dece070d28 feat(strava): add connection verified check 2023-04-07 14:16:30 +02:00
Ali BARIN
8be2ed0034 feat(strava): add current user utility function 2023-04-07 14:16:30 +02:00
Ali BARIN
37c6b57a48 feat(strava): add authentication support 2023-04-07 14:16:30 +02:00
Ali BARIN
8a0b5c24a5 feat(strava): add app definition 2023-04-07 14:16:30 +02:00
Ali BARIN
20431da757 Merge pull request #1037 from automatisch/dropbox-new-folders
feat(dropbox): add create folder and rename file actions
2023-04-07 14:16:10 +02:00
Ali BARIN
f219c87a9e feat(dropbox): elaborate on argument label 2023-04-07 12:12:30 +00:00
Ali BARIN
5d2134db56 fix(dropbox): don't add old token in refresh token logic 2023-04-07 11:58:06 +00:00
Ali BARIN
1dc46dd31c docs(dropbox): add connection and actions 2023-04-06 18:59:55 +00:00
Ali BARIN
96250ce9a0 refactor(spotify): import node:buffer 2023-04-06 18:59:55 +00:00
Ali BARIN
691682e09c feat(dropbox): add rename file action 2023-04-06 18:59:55 +00:00
Ali BARIN
814c504951 feat(dropbox): add create folder action 2023-04-06 18:59:55 +00:00
Ali BARIN
5c2b96a812 feat(dropbox): support connections 2023-04-06 18:59:55 +00:00
Ali BARIN
3dc1ca8adb Merge pull request #1041 from automatisch/list-shared-drives-for-new-files-in-folder
feat(google-drive): list shared drives for new files in folder trigger
2023-04-06 17:45:33 +02:00
Ali BARIN
a6850f1bc0 Merge pull request #1042 from automatisch/list-shared-drives-for-new-folders
feat(google-drive): list shared drives for new folders trigger
2023-04-06 17:44:25 +02:00
Ali BARIN
5ba6cb9135 Merge pull request #1043 from automatisch/list-shared-drives-for-updated-files
feat(google-drive): list shared drives for updated files trigger
2023-04-06 17:43:53 +02:00
Rıdvan Akca
500283af6f feat(google-drive): list shared drives for new files in folder trigger 2023-04-06 18:42:07 +03:00
Rıdvan Akca
43184dccda feat(google-drive): list shared drives for new folders trigger 2023-04-06 18:41:12 +03:00
Rıdvan Akca
4296a3b5df feat(google-drive): list shared drives for updated files trigger 2023-04-06 18:39:22 +03:00
Ali BARIN
14ba50f061 Merge pull request #1039 from automatisch/list-shared-drives-for-new-files
feat(google-drive): list shared drives for new files trigger
2023-04-06 16:35:59 +02:00
Rıdvan Akca
9ad7de56a3 feat(google-drive): list shared drives for new files trigger 2023-04-06 16:31:30 +02:00
Ali BARIN
5570687957 Merge pull request #1038 from automatisch/gsheet-new-spreadsheet
feat(google-sheets): add google sheets integration
2023-04-06 16:31:18 +02:00
Rıdvan Akca
1406bf3e10 docs(google-sheets): add trigger for new spreadsheets 2023-04-06 17:12:48 +03:00
Rıdvan Akca
287f299b94 feat(google-sheets): add new spreadsheets trigger 2023-04-06 17:12:48 +03:00
Rıdvan Akca
f88f05cd46 feat(google-sheets): add google sheets integration 2023-04-06 17:12:41 +03:00
Ali BARIN
c6fb42e2f3 Merge pull request #1036 from automatisch/update-full-name-in-profile-page
feat(ProfileSettings): add capability to update user full name
2023-04-06 15:37:51 +02:00
Ali BARIN
c64ca9d9b7 fix(ProfileSettings): don't submit password unless changed 2023-04-06 13:34:43 +00:00
Ali BARIN
7d47793afb feat: add subscription update and cancel logics 2023-04-06 13:08:49 +00:00
Rıdvan Akca
2840fce856 feat(profile): combine forms 2023-04-05 15:07:28 +03:00
Rıdvan Akca
2791dca412 feat(profile): add capability to update user's full name 2023-03-31 12:38:02 +03:00
Ali BARIN
3e268bf66b Merge pull request #1035 from automatisch/gdrive-updated-files-trigger
feat(google-drive): add updated files trigger
2023-03-30 16:26:27 +02:00
Rıdvan Akca
f44422a77e docs(google-drive): add trigger for updated files 2023-03-30 17:02:52 +03:00
Rıdvan Akca
4931cbcc34 feat(google-drive): add updated files trigger 2023-03-30 17:00:56 +03:00
Ali BARIN
59e495f7d3 Merge pull request #1034 from automatisch/gdrive-add-new-folders-trigger
docs(google-drive): add trigger for new folders
2023-03-30 13:26:31 +02:00
Rıdvan Akca
93866d7bc2 docs(google-drive): add trigger for new folders 2023-03-30 12:15:15 +03:00
Ali BARIN
ba1cdeaeeb Merge pull request #1016 from automatisch/add-docs-for-google-drive
docs: add google drive connection and triggers
2023-03-29 18:29:43 +02:00
Ali BARIN
234b27e555 Merge pull request #1015 from automatisch/add-docs-for-google-forms
docs: add google forms connection and triggers
2023-03-29 18:28:28 +02:00
Ali BARIN
5acfd09819 Merge pull request #1033 from automatisch/update-new-files-in-folder-trigger
fix(google-drive): show root files if no particular folder is selected
2023-03-29 18:25:39 +02:00
Rıdvan Akca
9923b15ecd fix(google-drive): show root files if no particular folder is selected 2023-03-29 19:01:04 +03:00
Ali BARIN
def5a6d9d0 Merge pull request #1032 from automatisch/gdrive-new-folder-trigger
feat(google-drive): add new folders trigger
2023-03-29 17:40:03 +02:00
Rıdvan Akca
17c8a405f5 feat(google-drive): add new folders trigger 2023-03-29 18:35:26 +03:00
Rıdvan Akca
ff874bfb48 docs(google-drive): add trigger for new files in folder 2023-03-29 15:11:39 +02:00
Ali BARIN
4cbb37b996 Merge pull request #1030 from automatisch/gdrive-update-new-files-in-folder-trigger
fix(google-drive): show only files in trigger for  new files in folder
2023-03-29 14:38:22 +02:00
Rıdvan Akca
f57e35c6b8 fix(google-drive): show only files in trigger for new files in folder 2023-03-29 15:33:21 +03:00
Ali BARIN
312e786e33 Merge pull request #1028 from automatisch/gdrive-new-file-in-folder-trigger
feat(google-drive): add new files in folder trigger
2023-03-29 13:01:45 +02:00
Rıdvan Akca
e2c75a2daf feat(google-drive): add new files in folder trigger 2023-03-29 13:16:39 +03:00
Ali BARIN
fce20263ea Merge pull request #1027 from automatisch/hide-invoices-when-empty
fix: hide empty invoices section
2023-03-27 23:39:27 +02:00
Faruk AYDIN
d841f9cb62 fix: Do not return invoices if there is no subscription 2023-03-28 00:27:43 +03:00
Ali BARIN
4945240ec2 fix: hide empty invoices section 2023-03-27 20:14:39 +00:00
Ali BARIN
f1cdc7e422 Merge pull request #1026 from automatisch/invoices-section
feat: add invoices on billing and usage page
2023-03-27 10:25:18 +02:00
Ali BARIN
372cdb10d6 feat: make invoices dynamic 2023-03-27 08:22:24 +00:00
Ali BARIN
fd24dbee21 fix(useBillingAndUsageData): fix invalid date 2023-03-27 08:22:14 +00:00
Faruk AYDIN
7066f7ea76 feat: Add draft design for invoices section 2023-03-27 01:02:28 +03:00
Ömer Faruk Aydın
5ae53c79b6 Merge pull request #1025 from automatisch/make-billing-and-usage-dynamic
feat: use actual data in billing and usage
2023-03-27 00:13:35 +03:00
Ali BARIN
f3a8ab289f feat: use actual data in billing and usage 2023-03-26 19:28:15 +00:00
Ali BARIN
a99609e3da feat(devcontainer): expose psql and redis 2023-03-26 19:26:35 +00:00
Ali BARIN
a271189448 refactor(devcontainer): configure urls using localhost 2023-03-26 19:26:13 +00:00
Ömer Faruk Aydın
251bc28526 Merge pull request #1024 from automatisch/billing-query
chore: Adjust billing query to be more dynamic
2023-03-26 18:55:07 +03:00
Faruk AYDIN
05505704e4 chore: Adjust billing query to be more dynamic 2023-03-26 18:22:49 +03:00
Ali BARIN
4aadcb021e Merge pull request #1023 from automatisch/refactor-billing-and-usage
refactor: getBillingAndUsage graphQL query
2023-03-26 16:06:16 +02:00
Faruk AYDIN
ae9da1b354 refactor: getBillingAndUsage graphQL query 2023-03-26 14:56:44 +03:00
Rıdvan Akca
a594029541 docs: add google drive connection and triggers 2023-03-26 14:32:31 +03:00
Rıdvan Akca
ddcb894932 docs: add google forms connection and triggers 2023-03-26 14:31:16 +03:00
Ömer Faruk Aydın
708cdbe545 Merge pull request #1022 from automatisch/get-invoices
feat: Implement getInvoices graphQL query
2023-03-26 14:16:56 +03:00
Faruk AYDIN
1cbf96dff1 feat: Implement getInvoices graphQL query 2023-03-26 02:02:36 +03:00
Ömer Faruk Aydın
bd55b37d5f Merge pull request #1021 from automatisch/paddle-client
Paddle client
2023-03-26 00:38:19 +03:00
Faruk AYDIN
7c3a0effee chore: Remove paddle-sdk dependency 2023-03-25 18:35:21 +03:00
Faruk AYDIN
fe3048aab0 feat: Implement getSubscription for paddle without sdk 2023-03-25 18:32:16 +03:00
Faruk AYDIN
9a5a3e879d fix: Use paddle_subscription_id as field name for findOne query 2023-03-25 17:40:45 +03:00
Faruk AYDIN
269902db94 chore: Use verify webhook logic without paddle sdk 2023-03-25 17:40:02 +03:00
Ömer Faruk Aydın
4ba67ea863 Merge pull request #1020 from automatisch/subscription-payment-succeeded
feat: Handle subscription payment succeeded event
2023-03-25 16:38:10 +03:00
Faruk AYDIN
3534712478 feat: Handle subscription payment succeeded event 2023-03-25 16:34:24 +03:00
Ömer Faruk Aydın
62b9a8071a Merge pull request #1019 from automatisch/expose-error-stack
fix: Expose error stack together with error message
2023-03-25 16:32:42 +03:00
Faruk AYDIN
cf83c27ca0 fix: Expose error stack together with error message 2023-03-25 14:01:04 +03:00
Ömer Faruk Aydın
8035f81f97 Merge pull request #1018 from automatisch/billing-and-usage-v2
feat: Add usage data to getBillingAndUsage graphQL query
2023-03-25 13:23:36 +03:00
Ömer Faruk Aydın
d2375fdc54 Merge pull request #1017 from automatisch/billing-and-usage
Billing and usage graphQL query
2023-03-25 13:23:21 +03:00
Faruk AYDIN
f07d9dd813 feat: Add usage data to getBillingAndUsage graphQL query 2023-03-25 02:16:17 +03:00
Faruk AYDIN
b5524b18cf feat: Implement draft version of getBillingAndUsage query 2023-03-25 00:39:49 +03:00
Faruk AYDIN
310497a5bf chore: Add deleted_at column to subscriptions 2023-03-25 00:07:43 +03:00
Faruk AYDIN
47a738d5e6 fix: Next bill amount for paddle 2023-03-25 00:07:28 +03:00
Ömer Faruk Aydın
6ad13c1da0 Merge pull request #1014 from automatisch/handle-subscription-created
feat: Handle subscription created webhook event
2023-03-24 13:58:36 +03:00
Faruk AYDIN
99454fdc4b feat: Handle subscription created webhook event 2023-03-24 13:48:15 +03:00
Ali BARIN
799f0ead6c Merge pull request #1013 from automatisch/google-drive-integration
feat(google-drive): add new files trigger
2023-03-23 17:56:07 +01:00
Rıdvan Akca
d16d709b72 feat(google-drive): add new files trigger 2023-03-23 19:10:43 +03:00
Ömer Faruk Aydın
8e1087b818 Merge pull request #1002 from automatisch/dynamic-field-schema
feat(http-request): add headers support
2023-03-23 18:32:17 +03:00
Ömer Faruk Aydın
139dcb521e Merge pull request #1012 from automatisch/create-subscription
feat: Create subscription model
2023-03-23 18:22:09 +03:00
Faruk AYDIN
b17e431473 feat: Create subscription model 2023-03-23 18:15:36 +03:00
Ali BARIN
b1ee3ef8ba feat(http-request): add headers support 2023-03-23 14:53:27 +00:00
Ali BARIN
75bbd16b0c feat: support interactive fields 2023-03-23 13:45:46 +00:00
Ömer Faruk Aydın
71a7943d01 Merge pull request #1004 from zntemel/spotify-app
feat: implement Spotify app
2023-03-23 16:01:51 +03:00
Ömer Faruk Aydın
d34785b5b0 Merge branch 'main' into spotify-app 2023-03-23 15:58:50 +03:00
Ali BARIN
af58ef7244 fix(spotify): correct create playlist action key 2023-03-22 20:39:26 +00:00
Ali BARIN
6d3bec8518 fix(spotify): do not override auth header on refresh token 2023-03-22 20:38:27 +00:00
Ömer Faruk Aydın
0346b157c5 Merge pull request #1010 from automatisch/remove-stripe-remainings
feat(UsageAlert): use new plan upgrade page
2023-03-22 22:47:19 +03:00
Ali BARIN
e25aab742b feat(UsageAlert): use new plan upgrade page 2023-03-21 20:25:46 +00:00
Ömer Faruk Aydın
6f2ca00263 Merge pull request #1008 from automatisch/paddle-webhook-verify
feat: Verify paddle webhooks
2023-03-21 22:20:28 +03:00
Faruk AYDIN
e1d26325f3 feat: Verify paddle webhooks 2023-03-21 22:17:45 +03:00
Ömer Faruk Aydın
4202f963c3 Merge pull request #1007 from automatisch/remove-stripe
chore: Remove stripe-related functionality
2023-03-21 11:58:38 +03:00
Faruk AYDIN
d3ef45db1b chore: Remove stripe-related functionality 2023-03-21 11:55:45 +03:00
Ömer Faruk Aydın
dec55709a3 Merge pull request #1006 from automatisch/aut-73
feat: add checkout process
2023-03-21 11:34:16 +03:00
Ali BARIN
b5ed984f05 feat: add checkout process 2023-03-20 23:24:04 +00:00
Ali BARIN
66d7baa126 fix: make Paddle vendor id number 2023-03-20 23:23:44 +00:00
Ömer Faruk Aydın
a2809a14c5 Merge pull request #1005 from automatisch/paddle-info
feat: Implement getPaddleInfo graphQL query
2023-03-21 01:05:25 +03:00
Faruk AYDIN
040ad9edb0 feat: Implement getPaddleInfo graphQL query 2023-03-21 00:55:27 +03:00
Ömer Faruk Aydın
46dbe009f2 Merge pull request #1003 from automatisch/billing-page
feat: redesign billing page
2023-03-21 00:53:15 +03:00
Ali BARIN
3598d43938 feat: make payment plans dynamic 2023-03-21 00:50:33 +03:00
Faruk AYDIN
f1358c7ad1 feat: add GetPaymentPlans graphQL query 2023-03-21 00:50:33 +03:00
Ali BARIN
40862fcd01 feat: move plan upgrade to its page 2023-03-21 00:50:31 +03:00
Faruk AYDIN
189432c228 feat: Implement draft version of UpgradeFreeTrial component 2023-03-21 00:49:28 +03:00
Faruk AYDIN
280d16f3d9 chore: Use luxon instead moment as date utility 2023-03-21 00:49:28 +03:00
Faruk AYDIN
722c39590f feat: Start trial period on cloud 2023-03-21 00:49:28 +03:00
Ali BARIN
b1138dbf05 feat: make billings page responsive 2023-03-21 00:49:28 +03:00
Faruk AYDIN
08918282a7 feat: redesign billing page 2023-03-21 00:49:28 +03:00
Ömer Faruk Aydın
5007b0bf1a Merge pull request #993 from automatisch/searchable-json
feat: add searchable json viewer component
2023-03-20 13:29:40 +03:00
Rıdvan Akca
0a5912eb8e refactor(SearchableJSONViewer): rewrite collecting keys and values of data 2023-03-20 11:31:01 +03:00
Zeynep Nur Temel
8acd7b03ed docs: add spotify connection and actions 2023-03-19 23:48:45 +03:00
Zeynep Nur Temel
ed87df212f feat: add refresh token for spotify 2023-03-19 23:04:55 +03:00
Zeynep Nur Temel
56243aa076 feat: implement create playlist action for spotify 2023-03-18 19:18:16 +03:00
Zeynep Nur Temel
41e9f32e1b feat: implement Spotify connection 2023-03-18 13:41:18 +03:00
Rıdvan Akca
f7753aa1b4 fix(SearchableJSONViewer): remove undefined values from filtered arrays 2023-03-17 19:38:10 +03:00
Rıdvan Akca
aebfcc38dd feat(SearchableJSONViewer): cover no result case 2023-03-17 19:38:10 +03:00
Rıdvan Akca
3e0149c058 feat: add searchable json viewer component 2023-03-17 19:38:10 +03:00
Ali BARIN
1e62e09825 Merge pull request #999 from automatisch/dependabot/npm_and_yarn/webpack-5.76.1
chore(deps): bump webpack from 5.67.0 to 5.76.1
2023-03-14 23:20:07 +01:00
dependabot[bot]
956b15a2eb chore(deps): bump webpack from 5.67.0 to 5.76.1
Bumps [webpack](https://github.com/webpack/webpack) from 5.67.0 to 5.76.1.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.67.0...v5.76.1)

---
updated-dependencies:
- dependency-name: webpack
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-14 20:08:01 +00:00
Ali BARIN
572b457b43 Merge pull request #955 from s-schumann/feature/signalwire-integration
feat: add Signalwire integration
2023-03-14 09:26:30 +01:00
Ali BARIN
7d40ae009f fix(signalwire): use incoming phone number instead of its sid 2023-03-13 22:32:25 +00:00
Ali BARIN
aec9595dea fix: stop checking empty app input in createFlow 2023-03-13 22:11:55 +00:00
Ali BARIN
cc90f19a46 fix: stop checking empty action/trigger input in updateStep 2023-03-13 22:11:42 +00:00
Ali BARIN
ab486c8ed1 Merge branch 'main' into feature/signalwire-integration 2023-03-13 21:14:14 +01:00
Ali BARIN
bfb1e817ec feat(signalwire): introduce dynamic incoming phone numbers 2023-03-13 20:13:45 +00:00
Ömer Faruk Aydın
ebc7c22388 Merge pull request #998 from automatisch/corporate-signup-in-loading-in-signup
fix: show progress as of sign-up in SignUpForm
2023-03-13 22:39:48 +03:00
Ali BARIN
10b4066c82 refactor(signalwire): add dynamic api base url 2023-03-13 19:10:20 +00:00
Ali BARIN
42dd67954d refactor(signalwire): utilize axios post params 2023-03-13 19:06:20 +00:00
Ali BARIN
d45fdf605f fix(signalwire): localize icon 2023-03-13 19:05:32 +00:00
Ali BARIN
caef9bb8b5 chore: reword Signalwire as SignalWire 2023-03-13 19:05:02 +00:00
Ali BARIN
c888934601 fix: show progress as of sign-up in SignUpForm 2023-03-13 16:28:04 +00:00
Ömer Faruk Aydın
95613b595e Merge pull request #997 from automatisch/change-redirect-url
chore: Change redirect url for payment portal
2023-03-12 19:28:57 +01:00
Ömer Faruk Aydın
9247bd9d9e Merge pull request #996 from automatisch/subscription-creation
chore: Make subscription creation synchronous
2023-03-12 19:27:56 +01:00
Faruk AYDIN
ee3d2489e6 chore: Change redirect url for payment portal 2023-03-12 21:26:10 +03:00
Faruk AYDIN
7764c4fbcb chore: Make subscription creation synchronous 2023-03-12 21:23:32 +03:00
Ömer Faruk Aydın
a18b524859 Merge pull request #995 from automatisch/backend-mutation-validations
feat: add validation checks in mutations
2023-03-12 18:53:32 +01:00
Ali BARIN
9b8ec9b85e feat: validate step input in executeFlow 2023-03-12 11:28:33 +00:00
Ali BARIN
7b3f070973 feat: validate app and action input in createStep 2023-03-12 11:26:50 +00:00
Ali BARIN
ba27fc12e8 feat: validate app input in createFlow 2023-03-12 11:26:28 +00:00
Ali BARIN
db55912f78 feat: validate trigger input in updateStep 2023-03-12 11:18:04 +00:00
Ali BARIN
4c4bd267d4 feat: validate action input in updateStep 2023-03-12 11:17:55 +00:00
Ali BARIN
dc1002659b feat: validate connection input in updateStep 2023-03-12 11:16:51 +00:00
Ali BARIN
97da370301 feat: provide clear message when app is not found 2023-03-12 09:59:27 +00:00
Ali BARIN
571901f333 Merge pull request #994 from automatisch/introduce-sentry
feat: introduce Sentry
2023-03-10 20:06:56 +01:00
Ali BARIN
4d90df9d9a feat: introduce Sentry 2023-03-10 18:53:10 +00:00
Ali BARIN
2c18667ffd Merge pull request #992 from automatisch/open-stripe-in-same-window
feat: open payment portal in same window
2023-03-09 16:53:43 +01:00
Ömer Faruk Aydın
89157cd606 Merge pull request #991 from automatisch/let-extra-executions-go-through-over-quota
feat: don't interrupt execution due to quota
2023-03-09 16:18:14 +01:00
Ali BARIN
37e0091ef0 feat: open payment portal in same window 2023-03-09 15:17:05 +00:00
Ali BARIN
ae3512fecf feat: don't interrupt execution due to quota 2023-03-09 15:13:50 +00:00
Ömer Faruk Aydın
8b81391e2f Merge pull request #989 from automatisch/skip-processing-steps-over-quota
feat: skip processing tasks over task quota
2023-03-09 13:51:11 +01:00
Ali BARIN
54e68f6252 feat: skip processing tasks over task quota 2023-03-09 12:48:16 +00:00
Ali BARIN
92d1ed65ff feat: expose errors from webhooks 2023-03-09 12:46:32 +00:00
Ali BARIN
8098a7ee5d feat: add checkIfLimitExceeded in UsageData model 2023-03-09 12:46:32 +00:00
Ömer Faruk Aydın
c24297630c Merge pull request #988 from automatisch/show-alert-for-task-usage
feat: show usage alert as of threshold
2023-03-09 13:45:55 +01:00
Ömer Faruk Aydın
a9d5212602 Merge pull request #990 from automatisch/disable-conditional-features
feat: disable non-cloud features
2023-03-09 13:45:27 +01:00
Ali BARIN
c81db8ae19 feat: disable non-cloud features 2023-03-09 11:23:29 +00:00
Sebastian Schumann
ecd356d42b Merge branch 'automatisch:main' into feature/signalwire-integration 2023-03-09 01:51:13 +01:00
Ali BARIN
e67adf87b2 fix: hide chatwoot in small screens 2023-03-08 18:43:45 +00:00
Ali BARIN
1669708041 feat: show usage alert as of threshold 2023-03-08 18:43:42 +00:00
Ali BARIN
12e34013f8 refactor: default noSsr to true in useMediaQuery 2023-03-08 18:35:51 +00:00
Ömer Faruk Aydın
bd2ad1d7a1 Merge pull request #986 from automatisch/increase-consumed-task-count
feat: increase consumed task count upon tasks
2023-03-08 10:07:28 +01:00
Ali BARIN
934cdb8237 refactor: increment consumed task count atomically 2023-03-08 01:45:22 +00:00
Ömer Faruk Aydın
2f6ea8830e Merge pull request #984 from automatisch/add-chatwoot
feat: add chatwoot
2023-03-07 23:21:47 +01:00
Ömer Faruk Aydın
b77d08ebbf Merge pull request #985 from automatisch/show-payment-plan-name
feat: show current plan name in usage information
2023-03-07 23:03:44 +01:00
Ömer Faruk Aydın
b735d32cbc Merge pull request #983 from automatisch/payment-webhooks
feat: Implement stripe webhooks
2023-03-07 23:03:14 +01:00
Ali BARIN
472ffd5b5c feat: increase consumed task count upon tasks 2023-03-07 21:26:12 +00:00
Ali BARIN
4567ca8fce feat: show current plan name in usage information 2023-03-07 20:48:57 +00:00
Ali BARIN
07ed24ca7a feat: add chatwoot 2023-03-07 20:36:54 +00:00
Ali BARIN
52575be2a7 chore: add fullName in User interface 2023-03-07 20:36:54 +00:00
Ali BARIN
c8187e52bb fix: stop redirecting to login on /login 2023-03-07 20:36:54 +00:00
Faruk AYDIN
b0b6b72b4c feat: Implement stripe webhooks 2023-03-07 17:22:40 +01:00
Ali BARIN
7676bc5836 Merge pull request #982 from automatisch/add-space-between-buttons
refactor: adjust spacing in FlowRow
2023-03-07 16:10:27 +01:00
Rıdvan Akca
f98c1725be refactor: adjust spacing between button and chip in FlowRow component 2023-03-07 18:06:36 +03:00
Ali BARIN
c6bd599b63 Merge pull request #981 from automatisch/fix-signup-email
fix: add custom email error message for signup
2023-03-07 13:30:06 +01:00
Rıdvan Akca
7908779c89 fix: add custom email error message for signup 2023-03-07 15:25:40 +03:00
Ali BARIN
29ad68afab Merge pull request #980 from automatisch/make-automatisch-info-public
fix: make getAutomatischInfo public
2023-03-06 22:46:13 +01:00
Ali BARIN
9aaeac6a08 fix: make getAutomatischInfo public 2023-03-06 21:41:53 +00:00
Ömer Faruk Aydın
8f074c2131 Merge pull request #979 from automatisch/show-usage-data
feat: show usage data
2023-03-06 22:27:34 +01:00
Ali BARIN
930653c86d feat: show usage data 2023-03-06 20:28:09 +00:00
Ömer Faruk Aydın
cc31b7c210 Merge pull request #977 from automatisch/check-cloud
feat: add empty billing and usage page
2023-03-06 20:49:44 +01:00
Ömer Faruk Aydın
bb19e9308c Merge pull request #978 from automatisch/expose-payment-portal
feat: show payment portal information
2023-03-06 20:46:58 +01:00
Ali BARIN
5e7b4bfe45 feat: show payment portal information 2023-03-06 19:42:41 +00:00
Ali BARIN
dbeeb61cc5 feat: add empty billing and usage page 2023-03-06 18:06:00 +00:00
Ali BARIN
26d8e5856a feat: make automatisch info available 2023-03-06 18:04:47 +00:00
Ali BARIN
66be6d1e89 fix: return isCloud boolean in getAutomatischInfo 2023-03-06 18:01:47 +00:00
Ömer Faruk Aydın
677aa232e7 Merge pull request #976 from automatisch/get-payment-portal-url
feat: Add getPaymentPortalUrl graphQL query
2023-03-06 13:22:09 +01:00
Faruk AYDIN
cabbb45031 feat: Add getPaymentPortalUrl graphQL query 2023-03-06 12:45:29 +01:00
Ömer Faruk Aydın
ba99df645b Merge pull request #975 from automatisch/get-usage-data
feat: Implement getUsageData graphQL query
2023-03-06 12:44:35 +01:00
Faruk AYDIN
282e5ba2d8 feat: Implement getUsageData graphQL query 2023-03-06 12:37:28 +01:00
Ömer Faruk Aydın
42d418da58 Merge pull request #974 from automatisch/change-consumer-task-count
fix: Alter consumed task count column to integer
2023-03-06 12:09:34 +01:00
Faruk AYDIN
cbf270fdba fix: Alter consumed task count column to integer 2023-03-06 11:35:39 +01:00
Ömer Faruk Aydın
bb9abe104f Merge pull request #973 from automatisch/get-automatisch-info-query
feat: Introduce getAutomatischInfo graphQL query
2023-03-05 20:17:59 +01:00
Faruk AYDIN
18f0d6dea3 feat: Introduce getAutomatischInfo graphQL query 2023-03-05 20:12:59 +01:00
Ali BARIN
23dc9a1139 Merge pull request #972 from automatisch/payment-draft
feat: Initial payment implementation
2023-03-05 18:14:03 +01:00
Faruk AYDIN
5e18ef5830 feat: Initial payment implementation 2023-03-05 17:14:01 +01:00
Ömer Faruk Aydın
63f8fc266d Merge pull request #968 from automatisch/forgot-password
feat: add forgot password featureset
2023-03-05 10:28:07 +01:00
Ali BARIN
4e46b16f7b feat: use full name instead of email address in emails 2023-03-05 09:18:07 +00:00
Ömer Faruk Aydın
ba19b50005 Merge pull request #971 from automatisch/reset-password-page
feat: reset password page
2023-03-04 19:33:10 +01:00
Ali BARIN
fa867387d4 feat: add reset password page 2023-03-03 20:13:31 +00:00
Ali BARIN
5762cf5dc5 fix: make resetMutation public 2023-03-03 20:09:37 +00:00
Ali BARIN
7d9f624805 feat: add translations for sign up CTA in login 2023-03-03 18:15:49 +00:00
Ali BARIN
5b335ccd59 feat: add translations in login form 2023-03-03 18:11:44 +00:00
Ali BARIN
4792853eb6 feat: add forgot password page 2023-03-03 18:10:52 +00:00
Ali BARIN
cc05bc7db8 feat: expose email address in email templates 2023-03-03 18:07:35 +00:00
Ali BARIN
d198eaa988 fix: correct template name in email worker 2023-03-03 18:07:35 +00:00
Ali BARIN
f094da6a4b feat: add spacing in reset password email template 2023-03-03 18:07:35 +00:00
Ali BARIN
a53961b235 fix: correct reset password link in email 2023-03-03 18:07:35 +00:00
Ali BARIN
f644113af8 Merge pull request #970 from automatisch/add-signup
feat: add signup page
2023-03-03 19:06:43 +01:00
Rıdvan Akca
e4a903ec07 refactor: add translation for texts in sign up page 2023-03-03 20:42:12 +03:00
Rıdvan Akca
9408fe2a07 feat: remove helperText from profile setting page if there is no error 2023-03-03 18:43:18 +03:00
Rıdvan Akca
394e747a88 feat: remove helperText if there is no error 2023-03-03 18:40:55 +03:00
Ömer Faruk Aydın
e91f12729b Merge pull request #967 from automatisch/automatisch-cloud
feat: Introduce automatisch cloud env variable
2023-03-03 12:28:37 +01:00
Faruk AYDIN
134e588b14 feat: Introduce automatisch cloud env variable 2023-03-03 12:15:58 +01:00
Rıdvan Akca
d061eb7b58 feat: update migration logic and remove consolelog 2023-03-03 14:10:54 +03:00
Rıdvan Akca
18089a8076 feat: add signup logic 2023-03-03 14:05:35 +03:00
Ömer Faruk Aydın
02236e01d9 Merge pull request #966 from automatisch/delete-account-in-web
feat: show delete user functionality
2023-03-03 10:11:48 +01:00
Rıdvan Akca
c1150d50b1 feat: create signup form 2023-03-03 11:34:53 +03:00
Ali BARIN
387a849269 fix: delete current user in deleteUser mutation 2023-03-02 23:46:20 +00:00
Ali BARIN
399cebda70 feat: show delete user functionality 2023-03-02 23:37:05 +00:00
Ali BARIN
72720b3dfe Merge pull request #965 from automatisch/add-deleteUser-mutation
Add deleteUser mutation
2023-03-02 22:08:44 +01:00
Ali BARIN
64f7560b3b feat: add delete user worker 2023-03-02 15:28:51 +00:00
Ali BARIN
e06b646f49 feat: add deleteUser mutation 2023-03-02 15:26:06 +00:00
Ali BARIN
b51d9bb17b feat: add delete user queue 2023-03-02 15:25:24 +00:00
Rıdvan Akca
4e967c5720 feat: add signup link to login and create signup dummy page 2023-03-02 16:42:50 +03:00
Ali BARIN
179db38fd1 feat: show email queue jobs in bull-board 2023-03-02 11:11:42 +00:00
Ömer Faruk Aydın
e51930d7e1 Merge pull request #964 from automatisch/add-full-name-to-users
feat: introduce full name column to user model
2023-03-01 23:33:03 +01:00
Ali BARIN
74a299dbe6 feat: introduce full name column to user model 2023-03-01 21:40:39 +00:00
Ali BARIN
282863c526 Merge pull request #889 from automatisch/feature/dynamic-fields
feat: add dynamic fields support
2023-03-01 19:31:23 +01:00
Ali BARIN
63d794ed3e refactor(dynamicFields): use thrown error instead of returning it 2023-03-01 18:00:27 +00:00
Ali BARIN
c6b8f12f9a feat(Editor): implement dynamic fields 2023-03-01 17:52:52 +00:00
Faruk AYDIN
d16e292231 feat: Add dynamic fields structure to step arguments 2023-02-28 22:22:48 +00:00
Ali BARIN
29a319a850 Merge pull request #962 from automatisch/fix-unreachable-server-errors
fix: correct errors for unreachable servers
2023-02-27 21:00:43 +01:00
Ali BARIN
c11e0db077 Merge pull request #961 from automatisch/use-custom-server-in-ntfy
fix(ntfy): use given server URL instead of default
2023-02-27 21:00:31 +01:00
Ali BARIN
b2dafb5dfa Merge pull request #960 from automatisch/remove-original-error-in-gql-responses
fix: stop using originalError in GQL responses
2023-02-27 21:00:18 +01:00
Ali BARIN
e7ccd01427 fix: stop using originalError in GQL responses 2023-02-27 20:29:22 +01:00
Ali BARIN
447f26458a fix(ntfy): use given server URL instead of default 2023-02-27 20:28:58 +01:00
Ali BARIN
61fb71a080 fix: correct errors for unreachable servers 2023-02-27 20:28:15 +01:00
Ömer Faruk Aydın
329fcdf8f4 Merge pull request #959 from automatisch/do-not-suppress-native-gql-errors
fix: do not suppress native GQL errors
2023-02-26 23:16:03 +01:00
Ali BARIN
37cdb34014 fix: do not suppress native GQL errors 2023-02-26 22:13:07 +00:00
Ali BARIN
5ee7b85cc4 feat(filter): add filter app 2023-02-26 17:02:30 +01:00
Ömer Faruk Aydın
1f7228f95a Merge pull request #958 from automatisch/fix-seed-user
fix: Create seed user with admin role
2023-02-26 16:03:38 +01:00
Faruk AYDIN
fa8418adcd fix: Create seed user with admin role 2023-02-26 15:40:47 +01:00
Ömer Faruk Aydın
d3b1765ffe Merge branch 'main' into feature/signalwire-integration 2023-02-26 13:23:27 +01:00
Ömer Faruk Aydın
0a274ebadb Merge pull request #957 from automatisch/oauth-docs
docs: Explain OAuth preference for app connections
2023-02-25 11:02:50 +01:00
Faruk AYDIN
0d1b35edc5 docs: Explain OAuth preference for app connections 2023-02-25 10:53:54 +01:00
Sebastian Schumann
d7e4ae53ce feat: add Signalwire integration 2023-02-24 12:45:58 +01:00
Ömer Faruk Aydın
61ceffc6f9 Merge pull request #956 from automatisch/frontend-app-start
docs: Add how to start frontend app for dev setup
2023-02-24 12:27:02 +01:00
Faruk AYDIN
0c422bfd21 docs: Add how to start frontend app for dev setup 2023-02-24 12:23:25 +01:00
Ömer Faruk Aydın
a0815b06a6 feat: Implement draft version of reset password email (#949) 2023-02-24 00:25:10 +01:00
कारतोफ्फेलस्क्रिप्ट™
bd02b7574a chore: Reduce the docker image size by half (#890) 2023-02-21 13:02:32 +01:00
Ömer Faruk Aydın
1048d923d0 Merge pull request #947 from automatisch/reset-password
feat: Add resetPassword mutation
2023-02-19 11:49:58 +01:00
Faruk AYDIN
9dbfcf4262 feat: Add resetPassword mutation 2023-02-19 11:04:56 +01:00
Ömer Faruk Aydın
8ea176b5f0 Merge pull request #946 from automatisch/forgot-password-mutation
feat: Implement forgotPassword mutation
2023-02-18 17:23:16 +01:00
Faruk AYDIN
90dcbadc52 feat: Implement forgotPassword mutation 2023-02-18 16:54:01 +01:00
Ömer Faruk Aydın
e4021bf830 Merge pull request #945 from automatisch/create-user
feat: Implement createUser mutation
2023-02-18 15:53:07 +01:00
Faruk AYDIN
b8b453aba0 feat: Implement createUser mutation 2023-02-18 14:49:12 +01:00
Ömer Faruk Aydın
aeec2377c1 Merge pull request #944 from automatisch/user-role
feat: Introduce role column to user model
2023-02-18 14:29:11 +01:00
Faruk AYDIN
f8b0ffd39b feat: Introduce role column to user model 2023-02-18 14:26:10 +01:00
Ömer Faruk Aydın
9953c3c823 Merge pull request #943 from automatisch/check-license
fix: Use kebab-case for check license file
2023-02-17 22:46:40 +01:00
Faruk AYDIN
d227a07fe9 fix: Use kebab-case for check license file 2023-02-17 22:42:56 +01:00
Ömer Faruk Aydın
7c394414d8 Merge pull request #939 from automatisch/verify-license
feat: Introduce getLicense graphQL query
2023-02-14 22:19:17 +01:00
Faruk AYDIN
1dfb22d02e feat: Introduce getLicense graphQL query 2023-02-14 22:15:59 +01:00
Faruk AYDIN
1847ad5622 chore: Use GitHub issues instead of discussions for requests 2023-02-13 21:48:35 +01:00
Ömer Faruk Aydın
beb701ceb4 Merge pull request #894 from automatisch/docs/license
chore: Explain license changes in docs and package.json files
2023-02-12 16:02:23 +01:00
Faruk AYDIN
2fbadea821 chore: Explain license changes in docs and package.json files 2023-02-12 15:46:31 +01:00
Ömer Faruk Aydın
bac561d8c7 Merge pull request #893 from automatisch/license-adjustment
chore: Adjust licenses
2023-02-12 14:39:07 +01:00
Faruk AYDIN
7bd261a02e chore: Adjust licenses 2023-02-12 13:41:24 +01:00
Ömer Faruk Aydın
df99a889f0 Merge pull request #892 from automatisch/cla-text
chore: Add contributor license agreement
2023-02-12 12:09:21 +01:00
Ömer Faruk Aydın
16c5892a1d Merge pull request #891 from automatisch/ee-license
chore: Introduce EE license
2023-02-12 12:09:08 +01:00
Faruk AYDIN
0236bbaf68 chore: Add contributor license agreement 2023-02-10 15:56:43 +01:00
Faruk AYDIN
2ca5a290c3 chore: Adjust licenses as CE and EE editions 2023-02-10 15:46:51 +01:00
Faruk AYDIN
66c388a644 chore: Introduce enterprise license 2023-02-10 15:31:54 +01:00
Ali BARIN
74a77ed271 fix(worker): compute parameters for delaying job 2023-02-08 23:22:57 +01:00
Ali BARIN
3d98e6cdc0 feat(slack/list-channels): add im channels and increase limit 2023-02-08 23:22:43 +01:00
Faruk AYDIN
831ae96e0f Release v.0.5.0 2023-02-08 13:32:25 +01:00
Faruk AYDIN
113a91a73f chore: Update versioning in dockerfiles 2023-02-08 13:32:25 +01:00
363 changed files with 10322 additions and 11551 deletions

View File

@@ -8,10 +8,8 @@ echo "Configuring backend environment variables..."
cd packages/backend
rm -rf .env
echo "
HOST=localhost
PROTOCOL=http
PORT=$BACKEND_PORT
WEB_APP_URL=https://$CODESPACE_NAME-$WEB_PORT.$GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN
WEB_APP_URL=http://localhost:$WEB_PORT
APP_ENV=development
POSTGRES_DATABASE=automatisch
POSTGRES_PORT=5432
@@ -30,8 +28,7 @@ cd packages/web
rm -rf .env
echo "
PORT=$WEB_PORT
REACT_APP_GRAPHQL_URL=https://$CODESPACE_NAME-$BACKEND_PORT.$GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN/graphql
REACT_APP_BASE_URL=https://$CODESPACE_NAME-$WEB_PORT.$GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN
REACT_APP_GRAPHQL_URL=http://localhost:$BACKEND_PORT/graphql
REACT_APP_NOTIFICATIONS_URL=https://notifications.automatisch.io
" >> .env
cd $CURRENT_DIR

View File

@@ -21,10 +21,18 @@ services:
interval: 10s
timeout: 5s
retries: 5
ports:
- '5432:5432'
expose:
- 5432
redis:
image: 'redis:7.0.4-alpine'
volumes:
- redis_data:/data
ports:
- '6379:6379'
expose:
- 6379
volumes:
postgres_data:

8
.dockerignore Normal file
View File

@@ -0,0 +1,8 @@
**/node_modules/
**/dist/
**/logs/
**/.devcontainer
**/.github
**/.vscode
packages/docs
packages/e2e-test

View File

@@ -0,0 +1,5 @@
# Automatisch Contributor License Agreement
I give Automatisch permission to license my contributions on any terms they like. I am giving them this license in order to make it possible for them to accept my contributions into their project.
**_As far as the law allows, my contributions come as is, without any warranty or condition, and I will not be liable to anyone for any damages related to this software or this license, under any kind of legal claim._**

3
LICENSE Normal file
View File

@@ -0,0 +1,3 @@
LICENSE.agpl (AGPL-3.0) applies to all files in this
repository, except for files that contain ".ee." in their name
which are covered by LICENSE.enterprise.

35
LICENSE.enterprise Normal file
View File

@@ -0,0 +1,35 @@
The Automatisch Enterprise license (the “Enterprise License”)
Copyright (c) 2023-present AB Software GmbH.
With regard to the Automatisch Software:
This software and associated documentation files (the "Software") may only be
used in production, if you (and any entity that you represent) have a valid
Automatisch Enterprise license for the correct number of user seats. Subject
to the foregoing sentence, you are free to modify this Software and publish
patches to the Software. You agree that Automatisch and/or its licensors
(as applicable) retain all right, title and interest in and to all such
modifications and/or patches, and all such modifications and/or patches may
only be used, copied, modified, displayed, distributed, or otherwise exploited
with a valid Automatisch Enterprise license for the correct number of user seats.
Notwithstanding the foregoing, you may copy and modify the Software for
development and testing purposes, without requiring a subscription. You agree
that Automatisch and/or its licensors (as applicable) retain all right, title
and interest in and to all such modifications. You are not granted any other
rights beyond what is expressly stated herein. Subject to the foregoing, it is
forbidden to copy, merge, publish, distribute, sublicense, and/or sell the Software.
The full text of this Enterprise License shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
For all third party components incorporated into the Automatisch Software, those
components are licensed under the original license provided by the owner of the
applicable component.

View File

@@ -44,10 +44,18 @@ For other installation types, you can check the [installation](https://automatis
## Support
If you have any questions or problems, please visit our GitHub discussions page, and we'll try to help you as soon as possible.
If you have any questions or problems, please visit our GitHub issues page, and we'll try to help you as soon as possible.
[https://github.com/automatisch/automatisch/discussions](https://github.com/automatisch/automatisch/discussions)
[https://github.com/automatisch/automatisch/issues](https://github.com/automatisch/automatisch/issues)
## License
Automatisch is an open-source software with the [AGPL 3.0 license](https://github.com/automatisch/automatisch/blob/main/LICENSE.md).
Automatisch Community Edition (Automatisch CE) is an open-source software with the [AGPL-3.0 license](LICENSE.agpl).
Automatisch Enterprise Edition (Automatisch EE) is a commercial offering with the [Enterprise license](LICENSE.enterprise).
The Automatisch repository contains both AGPL-licensed and Enterprise-licensed files. We maintain a single repository to make development easier.
All files that contain ".ee." in their name fall under the [Enterprise license](LICENSE.enterprise). All other files fall under the [AGPL-3.0 license](LICENSE.agpl).
See the [LICENSE](LICENSE) file for more information.

View File

@@ -2,13 +2,13 @@
FROM node:16-alpine
WORKDIR /automatisch
RUN apk --no-cache add --virtual build-dependencies python3 build-base
RUN \
apk --no-cache add --virtual build-dependencies python3 build-base && \
yarn global add @automatisch/cli@0.7.0 --network-timeout 1000000 && \
rm -rf /usr/local/share/.cache/ && \
apk del build-dependencies
COPY ./entrypoint.sh /entrypoint.sh
RUN yarn global add @automatisch/cli@0.4.0 --network-timeout 1000000
RUN apk del build-dependencies python3 build-base
EXPOSE 3000
ENTRYPOINT ["sh", "/entrypoint.sh"]

19
docker/Dockerfile.cloud Normal file
View File

@@ -0,0 +1,19 @@
# syntax=docker/dockerfile:1
FROM node:16-alpine
WORKDIR /automatisch
ENV PORT 3000
RUN ls -lna
# copy the app, note .dockerignore
COPY . ./
RUN yarn
RUN yarn lerna bootstrap
RUN yarn lerna run --scope=@*/{web,backend,cli} build
COPY ./docker/entrypoint-cloud.sh /entrypoint-cloud.sh
EXPOSE 3000
ENTRYPOINT ["sh", "/entrypoint-cloud.sh"]

View File

@@ -1,5 +1,5 @@
# syntax=docker/dockerfile:1
FROM automatischio/automatisch:0.4.0
FROM automatischio/automatisch:0.7.0
WORKDIR /automatisch
RUN apk add --no-cache openssl dos2unix

9
docker/entrypoint-cloud.sh Executable file
View File

@@ -0,0 +1,9 @@
#!/bin/sh
set -e
if [ -n "$WORKER" ]; then
yarn automatisch start-worker
else
yarn automatisch start
fi

View File

@@ -2,7 +2,7 @@
"packages": [
"packages/*"
],
"version": "0.4.0",
"version": "0.7.0",
"npmClient": "yarn",
"useWorkspaces": true,
"command": {

View File

@@ -1,6 +1,6 @@
{
"name": "@automatisch/root",
"license": "AGPL-3.0",
"license": "See LICENSE file",
"private": true,
"scripts": {
"start": "lerna run --stream --parallel --scope=@*/{web,backend} dev",

View File

@@ -12,6 +12,8 @@ export async function createUser(
const userParams = {
email,
password,
fullName: 'Initial admin',
role: 'admin',
};
try {

View File

@@ -12,6 +12,7 @@ const knexConfig = {
database: appConfig.postgresDatabase,
ssl: appConfig.postgresEnableSsl,
},
searchPath: [appConfig.postgresSchema],
pool: { min: 0, max: 20 },
migrations: {
directory: __dirname + '/src/db/migrations',

View File

@@ -1,7 +1,7 @@
{
"name": "@automatisch/backend",
"version": "0.4.0",
"license": "AGPL-3.0",
"version": "0.7.0",
"license": "See LICENSE file",
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
"scripts": {
"dev": "ts-node-dev --exit-child src/server.ts",
@@ -17,16 +17,18 @@
"db:migration:create": "knex migrate:make",
"db:rollback": "knex migrate:rollback",
"db:migrate": "knex migrate:latest",
"copy-statics": "copyfiles src/**/*.{graphql,json,svg} dist",
"copy-statics": "copyfiles src/**/*.{graphql,json,svg,hbs} dist",
"prepack": "yarn build",
"prebuild": "rm -rf ./dist"
},
"dependencies": {
"@automatisch/web": "^0.4.0",
"@automatisch/web": "^0.7.0",
"@bull-board/express": "^3.10.1",
"@graphql-tools/graphql-file-loader": "^7.3.4",
"@graphql-tools/load": "^7.5.2",
"@rudderstack/rudder-sdk-node": "^1.1.2",
"@sentry/node": "^7.42.0",
"@sentry/tracing": "^7.42.0",
"@types/luxon": "^2.3.1",
"ajv-formats": "^2.1.1",
"axios": "0.24.0",
@@ -45,17 +47,21 @@
"graphql-shield": "^7.5.0",
"graphql-tools": "^8.2.0",
"graphql-type-json": "^0.3.2",
"handlebars": "^4.7.7",
"http-errors": "~1.6.3",
"jsonwebtoken": "^9.0.0",
"knex": "^2.4.0",
"lodash.get": "^4.4.2",
"luxon": "2.5.2",
"memory-cache": "^0.2.0",
"morgan": "^1.10.0",
"multer": "1.4.5-lts.1",
"nodemailer": "6.7.0",
"oauth-1.0a": "^2.2.6",
"objection": "^3.0.0",
"pg": "^8.7.1",
"php-serialize": "^4.0.2",
"stripe": "^11.13.0",
"winston": "^3.7.1"
},
"contributors": [
@@ -94,7 +100,7 @@
"url": "https://github.com/automatisch/automatisch/issues"
},
"devDependencies": {
"@automatisch/types": "^0.4.0",
"@automatisch/types": "^0.7.0",
"@types/bcrypt": "^5.0.0",
"@types/bull": "^3.15.8",
"@types/cors": "^2.8.12",
@@ -103,6 +109,7 @@
"@types/http-errors": "^1.8.1",
"@types/jsonwebtoken": "^8.5.8",
"@types/lodash.get": "^4.4.6",
"@types/memory-cache": "^0.2.2",
"@types/morgan": "^1.9.3",
"@types/multer": "1.4.7",
"@types/node": "^16.10.2",

View File

@@ -1,9 +1,12 @@
import createError from 'http-errors';
import express from 'express';
import appConfig from './config/app';
import cors from 'cors';
import { IRequest } from '@automatisch/types';
import appConfig from './config/app';
import corsOptions from './config/cors-options';
import morgan from './helpers/morgan';
import * as Sentry from './helpers/sentry.ee';
import appAssetsHandler from './helpers/app-assets-handler';
import webUIHandler from './helpers/web-ui-handler';
import errorHandler from './helpers/error-handler';
@@ -14,12 +17,16 @@ import {
} from './helpers/create-bull-board-handler';
import injectBullBoardHandler from './helpers/inject-bull-board-handler';
import router from './routes';
import { IRequest } from '@automatisch/types';
createBullBoardHandler(serverAdapter);
const app = express();
Sentry.init(app);
Sentry.attachRequestHandler(app);
Sentry.attachTracingHandler(app);
injectBullBoardHandler(app, serverAdapter);
appAssetsHandler(app);
@@ -33,13 +40,15 @@ app.use(
},
})
);
app.use(express.urlencoded({
extended: false,
limit: appConfig.requestBodySizeLimit,
verify(req, res, buf) {
(req as IRequest).rawBody = buf;
},
}));
app.use(
express.urlencoded({
extended: true,
limit: appConfig.requestBodySizeLimit,
verify(req, res, buf) {
(req as IRequest).rawBody = buf;
},
})
);
app.use(cors(corsOptions));
app.use('/', router);
@@ -50,6 +59,8 @@ app.use(function (req, res, next) {
next(createError(404));
});
Sentry.attachErrorHandler(app);
app.use(errorHandler);
export default app;

View File

@@ -0,0 +1,36 @@
import path from 'node:path';
import defineAction from '../../../../helpers/define-action';
export default defineAction({
name: 'Create folder',
key: 'createFolder',
description: 'Create a new folder with the given parent folder and folder name',
arguments: [
{
label: 'Folder',
key: 'parentFolder',
type: 'string' as const,
required: true,
description: 'Enter the parent folder path, like /TextFiles/ or /Documents/Taxes/',
variables: true,
},
{
label: 'Folder Name',
key: 'folderName',
type: 'string' as const,
required: true,
description: 'Enter the name for the new folder',
variables: true,
},
],
async run($) {
const parentFolder = $.step.parameters.parentFolder as string;
const folderName = $.step.parameters.folderName as string;
const folderPath = path.join(parentFolder, folderName);
const response = await $.http.post('/2/files/create_folder_v2', { path: folderPath });
$.setActionItem({ raw: response.data });
},
});

View File

@@ -0,0 +1,4 @@
import createFolder from "./create-folder";
import renameFile from "./rename-file";
export default [createFolder, renameFile];

View File

@@ -0,0 +1,45 @@
import path from 'node:path';
import defineAction from '../../../../helpers/define-action';
export default defineAction({
name: 'Rename file',
key: 'renameFile',
description: 'Rename a file with the given file path and new name',
arguments: [
{
label: 'File Path',
key: 'filePath',
type: 'string' as const,
required: true,
description:
'Write the full path to the file such as /Folder1/File.pdf',
variables: true,
},
{
label: 'New Name',
key: 'newName',
type: 'string' as const,
required: true,
description: "Enter the new name for the file (without the extension, e.g., '.pdf')",
variables: true,
},
],
async run($) {
const filePath = $.step.parameters.filePath as string;
const newName = $.step.parameters.newName as string;
const fileObject = path.parse(filePath);
const newPath = path.format({
dir: fileObject.dir,
ext: fileObject.ext,
name: newName,
});
const response = await $.http.post('/2/files/move_v2', {
from_path: filePath,
to_path: newPath,
});
$.setActionItem({ raw: response.data.metadata });
},
});

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" aria-label="Dropbox" role="img" viewBox="0 0 512 512" fill="#0061ff">
<path d="M158 101l-99 63 295 188 99-63m-99-188l99 63-295 188-99-63m99 83l98 63 98-63-98-62z"/>
</svg>

After

Width:  |  Height:  |  Size: 213 B

View File

@@ -0,0 +1,22 @@
import { URLSearchParams } from 'url';
import { IField, IGlobalVariable } from '@automatisch/types';
import scopes from '../common/scopes';
export default async function generateAuthUrl($: IGlobalVariable) {
const oauthRedirectUrlField = $.app.auth.fields.find(
(field: IField) => field.key == 'oAuthRedirectUrl'
);
const callbackUrl = oauthRedirectUrlField.value as string;
const searchParams = new URLSearchParams({
client_id: $.auth.data.clientId as string,
redirect_uri: callbackUrl,
response_type: 'code',
scope: scopes.join(' '),
token_access_type: 'offline',
});
const url = `${$.app.baseUrl}/oauth2/authorize?${searchParams.toString()}`;
await $.auth.set({ url });
}

View File

@@ -0,0 +1,48 @@
import generateAuthUrl from './generate-auth-url';
import verifyCredentials from './verify-credentials';
import isStillVerified from './is-still-verified';
import refreshToken from './refresh-token';
export default {
fields: [
{
key: 'oAuthRedirectUrl',
label: 'OAuth Redirect URL',
type: 'string' as const,
required: true,
readOnly: true,
value: '{WEB_APP_URL}/app/dropbox/connections/add',
placeholder: null,
description:
'When asked to input an OAuth callback or redirect URL in Dropbox OAuth, enter the URL above.',
clickToCopy: true,
},
{
key: 'clientId',
label: 'App Key',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
clickToCopy: false,
},
{
key: 'clientSecret',
label: 'App Secret',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
clickToCopy: false,
},
],
generateAuthUrl,
verifyCredentials,
isStillVerified,
refreshToken,
};

View File

@@ -0,0 +1,9 @@
import { IGlobalVariable } from '@automatisch/types';
import getCurrentAccount from '../common/get-current-account';
const isStillVerified = async ($: IGlobalVariable) => {
const account = await getCurrentAccount($);
return !!account;
};
export default isStillVerified;

View File

@@ -0,0 +1,41 @@
import { Buffer } from 'node:buffer';
import { IGlobalVariable } from '@automatisch/types';
const refreshToken = async ($: IGlobalVariable) => {
const params = {
grant_type: 'refresh_token',
refresh_token: $.auth.data.refreshToken as string,
};
const basicAuthToken = Buffer
.from(`${$.auth.data.clientId}:${$.auth.data.clientSecret}`)
.toString('base64');
const { data } = await $.http.post(
'oauth2/token',
null,
{
params,
headers: {
Authorization: `Basic ${basicAuthToken}`
},
additionalProperties: {
skipAddingAuthHeader: true
}
}
);
const {
access_token: accessToken,
expires_in: expiresIn,
token_type: tokenType,
} = data;
await $.auth.set({
accessToken,
expiresIn,
tokenType,
});
};
export default refreshToken;

View File

@@ -0,0 +1,102 @@
import { IGlobalVariable, IField } from '@automatisch/types';
import getCurrentAccount from '../common/get-current-account';
type TAccount = {
account_id: string,
name: {
given_name: string,
surname: string,
familiar_name: string,
display_name: string,
abbreviated_name: string,
},
email: string,
email_verified: boolean,
disabled: boolean,
country: string,
locale: string,
referral_link: string,
is_paired: boolean,
account_type: {
".tag": string,
},
root_info: {
".tag": string,
root_namespace_id: string,
home_namespace_id: string,
},
}
const verifyCredentials = async ($: IGlobalVariable) => {
const oauthRedirectUrlField = $.app.auth.fields.find(
(field: IField) => field.key == 'oAuthRedirectUrl'
);
const redirectUrl = oauthRedirectUrlField.value as string;
const params = {
client_id: $.auth.data.clientId as string,
redirect_uri: redirectUrl,
client_secret: $.auth.data.clientSecret as string,
code: $.auth.data.code as string,
grant_type: 'authorization_code',
}
const { data: verifiedCredentials } = await $.http.post(
'/oauth2/token',
null,
{ params }
);
const {
access_token: accessToken,
refresh_token: refreshToken,
expires_in: expiresIn,
scope: scope,
token_type: tokenType,
account_id: accountId,
team_id: teamId,
id_token: idToken,
uid,
} = verifiedCredentials;
await $.auth.set({
accessToken,
refreshToken,
expiresIn,
scope,
tokenType,
accountId,
teamId,
idToken,
uid
});
const account = await getCurrentAccount($) as TAccount;
await $.auth.set({
accountId: account.account_id,
name: {
givenName: account.name.given_name,
surname: account.name.surname,
familiarName: account.name.familiar_name,
displayName: account.name.display_name,
abbreviatedName: account.name.abbreviated_name,
},
email: account.email,
emailVerified: account.email_verified,
disabled: account.disabled,
country: account.country,
locale: account.locale,
referralLink: account.referral_link,
isPaired: account.is_paired,
accountType: {
".tag": account.account_type['.tag'],
},
rootInfo: {
".tag": account.root_info['.tag'],
rootNamespaceId: account.root_info.root_namespace_id,
homeNamespaceId: account.root_info.home_namespace_id,
},
screenName: `${account.name.display_name} - ${account.email}`,
});
};
export default verifyCredentials;

View File

@@ -0,0 +1,13 @@
import { TBeforeRequest } from '@automatisch/types';
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
requestConfig.headers['Content-Type'] = 'application/json';
if (!requestConfig.additionalProperties?.skipAddingAuthHeader && $.auth.data?.accessToken) {
requestConfig.headers.Authorization = `Bearer ${$.auth.data.accessToken}`;
}
return requestConfig;
};
export default addAuthHeader;

View File

@@ -0,0 +1,8 @@
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
const getCurrentAccount = async ($: IGlobalVariable): Promise<IJSONObject> => {
const response = await $.http.post('/2/users/get_current_account', null);
return response.data;
};
export default getCurrentAccount;

View File

@@ -0,0 +1,8 @@
const scopes = [
'account_info.read',
'files.metadata.read',
'files.content.write',
'files.content.read',
];
export default scopes;

View File

View File

@@ -0,0 +1,18 @@
import defineApp from '../../helpers/define-app';
import addAuthHeader from './common/add-auth-header';
import auth from './auth';
import actions from './actions';
export default defineApp({
name: 'Dropbox',
key: 'dropbox',
iconUrl: '{BASE_URL}/apps/dropbox/assets/favicon.svg',
authDocUrl: 'https://automatisch.io/docs/apps/dropbox/connection',
supportsConnections: true,
baseUrl: 'https://dropbox.com',
apiBaseUrl: 'https://api.dropboxapi.com',
primaryColor: '0061ff',
beforeRequest: [addAuthHeader],
auth,
actions,
});

View File

@@ -0,0 +1,109 @@
import defineAction from '../../../../helpers/define-action';
type TGroupItem = {
key: string;
operator: keyof TOperators;
value: string;
id: string;
}
type TGroup = Record<'and', TGroupItem[]>;
const isEqual = (a: string, b: string) => a === b;
const isNotEqual = (a: string, b: string) => !isEqual(a, b);
const isGreaterThan = (a: string, b: string) => Number(a) > Number(b);
const isLessThan = (a: string, b: string) => Number(a) < Number(b);
const isGreaterThanOrEqual = (a: string, b: string) => Number(a) >= Number(b);
const isLessThanOrEqual = (a: string, b: string) => Number(a) <= Number(b);
const contains = (a: string, b: string) => a.includes(b);
const doesNotContain = (a: string, b: string) => !contains(a, b);
const shouldContinue = (orGroups: TGroup[]) => {
let atLeastOneGroupMatches = false;
for (const group of orGroups) {
let groupMatches = true;
for (const condition of group.and) {
const conditionMatches = operate(
condition.operator,
condition.key,
condition.value
);
if (!conditionMatches) {
groupMatches = false;
break;
}
}
if (groupMatches) {
atLeastOneGroupMatches = true;
break;
}
}
return atLeastOneGroupMatches;
}
type TOperatorFunc = (a: string, b: string) => boolean;
type TOperators = {
equal: TOperatorFunc;
not_equal: TOperatorFunc;
greater_than: TOperatorFunc;
less_than: TOperatorFunc;
greater_than_or_equal: TOperatorFunc;
less_than_or_equal: TOperatorFunc;
contains: TOperatorFunc;
not_contains: TOperatorFunc;
};
const operators: TOperators = {
'equal': isEqual,
'not_equal': isNotEqual,
'greater_than': isGreaterThan,
'less_than': isLessThan,
'greater_than_or_equal': isGreaterThanOrEqual,
'less_than_or_equal': isLessThanOrEqual,
'contains': contains,
'not_contains': doesNotContain,
};
const operate = (operation: keyof TOperators, a: string, b: string) => {
return operators[operation](a, b);
};
export default defineAction({
name: 'Continue if conditions match',
key: 'continueIfMatches',
description: 'Let the execution continue if the conditions match',
arguments: [],
async run($) {
const orGroups = $.step.parameters.or as TGroup[];
const matchingGroups = orGroups.reduce((groups, group) => {
const matchingConditions = group.and
.filter((condition) => operate(condition.operator, condition.key, condition.value));
if (matchingConditions.length) {
return groups.concat([{ and: matchingConditions }]);
}
return groups;
}, []);
if (!shouldContinue(orGroups)) {
$.execution.exit();
}
$.setActionItem({
raw: {
or: matchingGroups,
}
});
},
});

View File

@@ -0,0 +1,3 @@
import continueIfMatches from './continue';
export default [continueIfMatches];

View File

@@ -0,0 +1,8 @@
<svg width="800px" height="800px" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Shape" fill="#000000" transform="translate(42.666667, 85.333333)">
<path d="M3.55271368e-14,1.42108547e-14 L191.565013,234.666667 L192,234.666667 L192,384 L234.666667,384 L234.666667,234.666667 L426.666667,1.42108547e-14 L3.55271368e-14,1.42108547e-14 Z M214.448,192 L211.81248,192 L89.9076267,42.6666667 L336.630187,42.6666667 L214.448,192 Z">
</path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 628 B

View File

View File

@@ -0,0 +1,14 @@
import defineApp from '../../helpers/define-app';
import actions from './actions';
export default defineApp({
name: 'Filter',
key: 'filter',
iconUrl: '{BASE_URL}/apps/filter/assets/favicon.svg',
authDocUrl: 'https://automatisch.io/docs/apps/filter/connection',
supportsConnections: false,
baseUrl: '',
apiBaseUrl: '',
primaryColor: '001F52',
actions,
});

View File

@@ -0,0 +1,8 @@
<svg viewBox="0 0 87.3 78" xmlns="http://www.w3.org/2000/svg">
<path d="m6.6 66.85 3.85 6.65c.8 1.4 1.95 2.5 3.3 3.3l13.75-23.8h-27.5c0 1.55.4 3.1 1.2 4.5z" fill="#0066da"/>
<path d="m43.65 25-13.75-23.8c-1.35.8-2.5 1.9-3.3 3.3l-25.4 44a9.06 9.06 0 0 0 -1.2 4.5h27.5z" fill="#00ac47"/>
<path d="m73.55 76.8c1.35-.8 2.5-1.9 3.3-3.3l1.6-2.75 7.65-13.25c.8-1.4 1.2-2.95 1.2-4.5h-27.502l5.852 11.5z" fill="#ea4335"/>
<path d="m43.65 25 13.75-23.8c-1.35-.8-2.9-1.2-4.5-1.2h-18.5c-1.6 0-3.15.45-4.5 1.2z" fill="#00832d"/>
<path d="m59.8 53h-32.3l-13.75 23.8c1.35.8 2.9 1.2 4.5 1.2h50.8c1.6 0 3.15-.45 4.5-1.2z" fill="#2684fc"/>
<path d="m73.4 26.5-12.7-22c-.8-1.4-1.95-2.5-3.3-3.3l-13.75 23.8 16.15 28h27.45c0-1.55-.4-3.1-1.2-4.5z" fill="#ffba00"/>
</svg>

After

Width:  |  Height:  |  Size: 755 B

View File

@@ -0,0 +1,24 @@
import { IField, IGlobalVariable } from '@automatisch/types';
import { URLSearchParams } from 'url';
import authScope from '../common/auth-scope';
export default async function generateAuthUrl($: IGlobalVariable) {
const oauthRedirectUrlField = $.app.auth.fields.find(
(field: IField) => field.key == 'oAuthRedirectUrl'
);
const redirectUri = oauthRedirectUrlField.value as string;
const searchParams = new URLSearchParams({
client_id: $.auth.data.clientId as string,
redirect_uri: redirectUri,
prompt: 'select_account',
scope: authScope.join(' '),
response_type: 'code',
access_type: 'offline',
});
const url = `https://accounts.google.com/o/oauth2/v2/auth?${searchParams.toString()}`;
await $.auth.set({
url,
});
}

View File

@@ -0,0 +1,48 @@
import generateAuthUrl from './generate-auth-url';
import verifyCredentials from './verify-credentials';
import refreshToken from './refresh-token';
import isStillVerified from './is-still-verified';
export default {
fields: [
{
key: 'oAuthRedirectUrl',
label: 'OAuth Redirect URL',
type: 'string' as const,
required: true,
readOnly: true,
value: '{WEB_APP_URL}/app/google-drive/connections/add',
placeholder: null,
description:
'When asked to input a redirect URL in Google Cloud, enter the URL above.',
clickToCopy: true,
},
{
key: 'clientId',
label: 'Client ID',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
clickToCopy: false,
},
{
key: 'clientSecret',
label: 'Client Secret',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
clickToCopy: false,
},
],
generateAuthUrl,
verifyCredentials,
isStillVerified,
refreshToken,
};

View File

@@ -0,0 +1,9 @@
import { IGlobalVariable } from '@automatisch/types';
import getCurrentUser from '../common/get-current-user';
const isStillVerified = async ($: IGlobalVariable) => {
const currentUser = await getCurrentUser($);
return !!currentUser.resourceName;
};
export default isStillVerified;

View File

@@ -0,0 +1,26 @@
import { URLSearchParams } from 'node:url';
import { IGlobalVariable } from '@automatisch/types';
import authScope from '../common/auth-scope';
const refreshToken = async ($: IGlobalVariable) => {
const params = new URLSearchParams({
client_id: $.auth.data.clientId as string,
client_secret: $.auth.data.clientSecret as string,
grant_type: 'refresh_token',
refresh_token: $.auth.data.refreshToken as string,
});
const { data } = await $.http.post(
'https://oauth2.googleapis.com/token',
params.toString()
);
await $.auth.set({
accessToken: data.access_token,
expiresIn: data.expires_in,
scope: authScope.join(' '),
tokenType: data.token_type,
});
};
export default refreshToken;

View File

@@ -0,0 +1,57 @@
import { IField, IGlobalVariable } from '@automatisch/types';
import getCurrentUser from '../common/get-current-user';
type TUser = {
displayName: string;
metadata: {
primary: boolean;
};
};
type TEmailAddress = {
value: string;
metadata: {
primary: boolean;
};
};
const verifyCredentials = async ($: IGlobalVariable) => {
const oauthRedirectUrlField = $.app.auth.fields.find(
(field: IField) => field.key == 'oAuthRedirectUrl'
);
const redirectUri = oauthRedirectUrlField.value as string;
const { data } = await $.http.post(`https://oauth2.googleapis.com/token`, {
client_id: $.auth.data.clientId,
client_secret: $.auth.data.clientSecret,
code: $.auth.data.code,
grant_type: 'authorization_code',
redirect_uri: redirectUri,
});
await $.auth.set({
accessToken: data.access_token,
tokenType: data.token_type,
});
const currentUser = await getCurrentUser($);
const { displayName } = currentUser.names.find(
(name: TUser) => name.metadata.primary
);
const { value: email } = currentUser.emailAddresses.find(
(emailAddress: TEmailAddress) => emailAddress.metadata.primary
);
await $.auth.set({
clientId: $.auth.data.clientId,
clientSecret: $.auth.data.clientSecret,
scope: $.auth.data.scope,
idToken: data.id_token,
expiresIn: data.expires_in,
refreshToken: data.refresh_token,
resourceName: currentUser.resourceName,
screenName: `${displayName} - ${email}`,
});
};
export default verifyCredentials;

View File

@@ -0,0 +1,11 @@
import { TBeforeRequest } from '@automatisch/types';
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
if ($.auth.data?.accessToken) {
requestConfig.headers.Authorization = `${$.auth.data.tokenType} ${$.auth.data.accessToken}`;
}
return requestConfig;
};
export default addAuthHeader;

View File

@@ -0,0 +1,7 @@
const authScope: string[] = [
'https://www.googleapis.com/auth/drive',
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/userinfo.profile',
];
export default authScope;

View File

@@ -0,0 +1,10 @@
import { IGlobalVariable } from '@automatisch/types';
const getCurrentUser = async ($: IGlobalVariable) => {
const { data: currentUser } = await $.http.get(
'https://people.googleapis.com/v1/people/me?personFields=names,emailAddresses'
);
return currentUser;
};
export default getCurrentUser;

View File

@@ -0,0 +1,4 @@
import listFolders from './list-folders';
import listDrives from './list-drives';
export default [listFolders, listDrives];

View File

@@ -0,0 +1,35 @@
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
export default {
name: 'List drives',
key: 'listDrives',
async run($: IGlobalVariable) {
const drives: {
data: IJSONObject[];
} = {
data: [{ value: null, name: 'My Google Drive' }],
};
const params = {
pageSize: 100,
pageToken: undefined as unknown as string,
};
do {
const { data } = await $.http.get(`/v3/drives`, { params });
params.pageToken = data.nextPageToken;
if (data.drives) {
for (const drive of data.drives) {
drives.data.push({
value: drive.id,
name: drive.name,
});
}
}
} while (params.pageToken);
return drives;
},
};

View File

@@ -0,0 +1,46 @@
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
export default {
name: 'List Folders',
key: 'listFolders',
async run($: IGlobalVariable) {
const folders: {
data: IJSONObject[];
} = {
data: [],
};
const params: Record<string, unknown> = {
q: `mimeType='application/vnd.google-apps.folder'`,
orderBy: 'createdTime desc',
pageToken: undefined as unknown as string,
pageSize: 1000,
driveId: $.step.parameters.driveId,
supportsAllDrives: true,
};
if ($.step.parameters.driveId) {
params.includeItemsFromAllDrives = true;
}
do {
const { data } = await $.http.get(
`https://www.googleapis.com/drive/v3/files`,
{
params,
}
);
params.pageToken = data.nextPageToken;
for (const file of data.files) {
folders.data.push({
value: file.id,
name: file.name,
});
}
} while (params.pageToken);
return folders;
},
};

View File

View File

@@ -0,0 +1,20 @@
import defineApp from '../../helpers/define-app';
import addAuthHeader from './common/add-auth-header';
import auth from './auth';
import triggers from './triggers';
import dynamicData from './dynamic-data';
export default defineApp({
name: 'Google Drive',
key: 'google-drive',
baseUrl: 'https://drive.google.com',
apiBaseUrl: 'https://www.googleapis.com/drive',
iconUrl: '{BASE_URL}/apps/google-drive/assets/favicon.svg',
authDocUrl: 'https://automatisch.io/docs/apps/google-drive/connection',
primaryColor: '1FA463',
supportsConnections: true,
beforeRequest: [addAuthHeader],
auth,
triggers,
dynamicData,
});

View File

@@ -0,0 +1,6 @@
import newFiles from './new-files';
import newFilesInFolder from './new-files-in-folder';
import newFolders from './new-folders';
import updatedFiles from './updated-files';
export default [newFiles, newFilesInFolder, newFolders, updatedFiles];

View File

@@ -0,0 +1,59 @@
import defineTrigger from '../../../../helpers/define-trigger';
import newFilesInFolder from './new-files-in-folder';
export default defineTrigger({
name: 'New Files in Folder',
key: 'newFilesInFolder',
pollInterval: 15,
description:
'Triggers when a new file is added directly to a specific folder (but not its subfolder).',
arguments: [
{
label: 'Drive',
key: 'driveId',
type: 'dropdown' as const,
required: false,
description:
'The Google Drive where your file resides. If nothing is selected, then your personal Google Drive will be used.',
variables: false,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listDrives',
},
],
},
},
{
label: 'Folder',
key: 'folderId',
type: 'dropdown' as const,
required: false,
dependsOn: ['parameters.driveId'],
description:
'Check a specific folder for new files. Please note: new files added to subfolders inside the folder you choose here will NOT trigger this flow. Defaults to the top-level folder if none is picked.',
variables: false,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listFolders',
},
{
name: 'parameters.driveId',
value: '{parameters.driveId}',
},
],
},
},
],
async run($) {
await newFilesInFolder($);
},
});

View File

@@ -0,0 +1,41 @@
import { IGlobalVariable } from '@automatisch/types';
const newFilesInFolder = async ($: IGlobalVariable) => {
let q = "mimeType!='application/vnd.google-apps.folder'";
if ($.step.parameters.folderId) {
q += ` and '${$.step.parameters.folderId}' in parents`;
} else {
q += ` and parents in 'root'`;
}
const params: Record<string, unknown> = {
pageToken: undefined as unknown as string,
orderBy: 'createdTime desc',
fields: '*',
pageSize: 1000,
q,
driveId: $.step.parameters.driveId,
supportsAllDrives: true,
};
if ($.step.parameters.driveId) {
params.includeItemsFromAllDrives = true;
}
do {
const { data } = await $.http.get(`/v3/files`, { params });
params.pageToken = data.nextPageToken;
if (data.files?.length) {
for (const file of data.files) {
$.pushTriggerItem({
raw: file,
meta: {
internalId: file.id,
},
});
}
}
} while (params.pageToken);
};
export default newFilesInFolder;

View File

@@ -0,0 +1,34 @@
import defineTrigger from '../../../../helpers/define-trigger';
import newFiles from './new-files';
export default defineTrigger({
name: 'New Files',
key: 'newFiles',
pollInterval: 15,
description: 'Triggers when any new file is added (inside of any folder).',
arguments: [
{
label: 'Drive',
key: 'driveId',
type: 'dropdown' as const,
required: false,
description:
'The Google Drive where your file resides. If nothing is selected, then your personal Google Drive will be used.',
variables: false,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listDrives',
},
],
},
},
],
async run($) {
await newFiles($);
},
});

View File

@@ -0,0 +1,35 @@
import { IGlobalVariable } from '@automatisch/types';
const newFiles = async ($: IGlobalVariable) => {
const params: Record<string, unknown> = {
pageToken: undefined as unknown as string,
orderBy: 'createdTime desc',
fields: '*',
pageSize: 1000,
q: `mimeType!='application/vnd.google-apps.folder'`,
driveId: $.step.parameters.driveId,
supportsAllDrives: true,
};
if ($.step.parameters.driveId) {
params.includeItemsFromAllDrives = true;
}
do {
const { data } = await $.http.get('/v3/files', { params });
params.pageToken = data.nextPageToken;
if (data.files?.length) {
for (const file of data.files) {
$.pushTriggerItem({
raw: file,
meta: {
internalId: file.id,
},
});
}
}
} while (params.pageToken);
};
export default newFiles;

View File

@@ -0,0 +1,59 @@
import defineTrigger from '../../../../helpers/define-trigger';
import newFolders from './new-folders';
export default defineTrigger({
name: 'New Folders',
key: 'newFolders',
pollInterval: 15,
description:
'Triggers when a new folder is added directly to a specific folder (but not its subfolder).',
arguments: [
{
label: 'Drive',
key: 'driveId',
type: 'dropdown' as const,
required: false,
description:
'The Google Drive where your file resides. If nothing is selected, then your personal Google Drive will be used.',
variables: false,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listDrives',
},
],
},
},
{
label: 'Folder',
key: 'folderId',
type: 'dropdown' as const,
required: false,
dependsOn: ['parameters.driveId'],
description:
'Check a specific folder for new subfolders. Please note: new folders added to subfolders inside the folder you choose here will NOT trigger this flow. Defaults to the top-level folder if none is picked.',
variables: false,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listFolders',
},
{
name: 'parameters.driveId',
value: '{parameters.driveId}',
},
],
},
},
],
async run($) {
await newFolders($);
},
});

View File

@@ -0,0 +1,42 @@
import { IGlobalVariable } from '@automatisch/types';
const newFolders = async ($: IGlobalVariable) => {
let q = "mimeType='application/vnd.google-apps.folder'";
if ($.step.parameters.folderId) {
q += ` and '${$.step.parameters.folderId}' in parents`;
} else {
q += ` and parents in 'root'`;
}
const params: Record<string, unknown> = {
pageToken: undefined as unknown as string,
orderBy: 'createdTime desc',
fields: '*',
pageSize: 1000,
q,
driveId: $.step.parameters.driveId,
supportsAllDrives: true,
};
if ($.step.parameters.driveId) {
params.includeItemsFromAllDrives = true;
}
do {
const { data } = await $.http.get(`/v3/files`, { params });
params.pageToken = data.nextPageToken;
if (data.files?.length) {
for (const file of data.files) {
$.pushTriggerItem({
raw: file,
meta: {
internalId: file.id,
},
});
}
}
} while (params.pageToken);
};
export default newFolders;

View File

@@ -0,0 +1,76 @@
import defineTrigger from '../../../../helpers/define-trigger';
import updatedFiles from './updated-files';
export default defineTrigger({
name: 'Updated Files',
key: 'updatedFiles',
pollInterval: 15,
description:
'Triggers when a file is updated in a specific folder (but not its subfolder).',
arguments: [
{
label: 'Drive',
key: 'driveId',
type: 'dropdown' as const,
required: false,
description:
'The Google Drive where your file resides. If nothing is selected, then your personal Google Drive will be used.',
variables: false,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listDrives',
},
],
},
},
{
label: 'Folder',
key: 'folderId',
type: 'dropdown' as const,
required: false,
dependsOn: ['parameters.driveId'],
description:
'Check a specific folder for updated files. Please note: files located in subfolders of the folder you choose here will NOT trigger this flow. Defaults to the top-level folder if none is picked.',
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listFolders',
},
{
name: 'parameters.driveId',
value: '{parameters.driveId}',
},
],
},
},
{
label: 'Include Deleted',
key: 'includeDeleted',
type: 'dropdown' as const,
required: true,
value: true,
description: 'Should this trigger also on files that are deleted?',
options: [
{
label: 'Yes',
value: true,
},
{
label: 'No',
value: false,
},
],
},
],
async run($) {
await updatedFiles($);
},
});

View File

@@ -0,0 +1,46 @@
import { IGlobalVariable } from '@automatisch/types';
const updatedFiles = async ($: IGlobalVariable) => {
let q = `mimeType!='application/vnd.google-apps.folder'`;
if ($.step.parameters.includeDeleted === false) {
q += ` and trashed=${$.step.parameters.includeDeleted}`;
}
if ($.step.parameters.folderId) {
q += ` and '${$.step.parameters.folderId}' in parents`;
} else {
q += ` and parents in 'root'`;
}
const params: Record<string, unknown> = {
pageToken: undefined as unknown as string,
orderBy: 'modifiedTime desc',
fields: '*',
pageSize: 1000,
q,
driveId: $.step.parameters.driveId,
supportsAllDrives: true,
};
if ($.step.parameters.driveId) {
params.includeItemsFromAllDrives = true;
}
do {
const { data } = await $.http.get(`/v3/files`, { params });
params.pageToken = data.nextPageToken;
if (data.files?.length) {
for (const file of data.files) {
$.pushTriggerItem({
raw: file,
meta: {
internalId: `${file.id}-${file.modifiedTime}`,
},
});
}
}
} while (params.pageToken);
};
export default updatedFiles;

View File

@@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="49px" height="67px" viewBox="0 0 49 67" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 54.1 (76490) - https://sketchapp.com -->
<title>Sheets-icon</title>
<desc>Created with Sketch.</desc>
<defs>
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z" id="path-1"></path>
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z" id="path-3"></path>
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z" id="path-5"></path>
<linearGradient x1="50.0053945%" y1="8.58610612%" x2="50.0053945%" y2="100.013939%" id="linearGradient-7">
<stop stop-color="#263238" stop-opacity="0.2" offset="0%"></stop>
<stop stop-color="#263238" stop-opacity="0.02" offset="100%"></stop>
</linearGradient>
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z" id="path-8"></path>
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z" id="path-10"></path>
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z" id="path-12"></path>
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z" id="path-14"></path>
<radialGradient cx="3.16804688%" cy="2.71744318%" fx="3.16804688%" fy="2.71744318%" r="161.248516%" gradientTransform="translate(0.031680,0.027174),scale(1.000000,0.727273),translate(-0.031680,-0.027174)" id="radialGradient-16">
<stop stop-color="#FFFFFF" stop-opacity="0.1" offset="0%"></stop>
<stop stop-color="#FFFFFF" stop-opacity="0" offset="100%"></stop>
</radialGradient>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Consumer-Apps-Sheets-Large-VD-R8-" transform="translate(-451.000000, -451.000000)">
<g id="Hero" transform="translate(0.000000, 63.000000)">
<g id="Personal" transform="translate(277.000000, 299.000000)">
<g id="Sheets-icon" transform="translate(174.833333, 89.958333)">
<g id="Group">
<g id="Clipped">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<g id="SVGID_1_"></g>
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L36.9791667,10.3541667 L29.5833333,0 Z" id="Path" fill="#0F9D58" fill-rule="nonzero" mask="url(#mask-2)"></path>
</g>
<g id="Clipped">
<mask id="mask-4" fill="white">
<use xlink:href="#path-3"></use>
</mask>
<g id="SVGID_1_"></g>
<path d="M11.8333333,31.8020833 L11.8333333,53.25 L35.5,53.25 L35.5,31.8020833 L11.8333333,31.8020833 Z M22.1875,50.2916667 L14.7916667,50.2916667 L14.7916667,46.59375 L22.1875,46.59375 L22.1875,50.2916667 Z M22.1875,44.375 L14.7916667,44.375 L14.7916667,40.6770833 L22.1875,40.6770833 L22.1875,44.375 Z M22.1875,38.4583333 L14.7916667,38.4583333 L14.7916667,34.7604167 L22.1875,34.7604167 L22.1875,38.4583333 Z M32.5416667,50.2916667 L25.1458333,50.2916667 L25.1458333,46.59375 L32.5416667,46.59375 L32.5416667,50.2916667 Z M32.5416667,44.375 L25.1458333,44.375 L25.1458333,40.6770833 L32.5416667,40.6770833 L32.5416667,44.375 Z M32.5416667,38.4583333 L25.1458333,38.4583333 L25.1458333,34.7604167 L32.5416667,34.7604167 L32.5416667,38.4583333 Z" id="Shape" fill="#F1F1F1" fill-rule="nonzero" mask="url(#mask-4)"></path>
</g>
<g id="Clipped">
<mask id="mask-6" fill="white">
<use xlink:href="#path-5"></use>
</mask>
<g id="SVGID_1_"></g>
<polygon id="Path" fill="url(#linearGradient-7)" fill-rule="nonzero" mask="url(#mask-6)" points="30.8813021 16.4520313 47.3333333 32.9003646 47.3333333 17.75"></polygon>
</g>
<g id="Clipped">
<mask id="mask-9" fill="white">
<use xlink:href="#path-8"></use>
</mask>
<g id="SVGID_1_"></g>
<g id="Group" mask="url(#mask-9)">
<g transform="translate(26.625000, -2.958333)">
<path d="M2.95833333,2.95833333 L2.95833333,16.2708333 C2.95833333,18.7225521 4.94411458,20.7083333 7.39583333,20.7083333 L20.7083333,20.7083333 L2.95833333,2.95833333 Z" id="Path" fill="#87CEAC" fill-rule="nonzero"></path>
</g>
</g>
</g>
<g id="Clipped">
<mask id="mask-11" fill="white">
<use xlink:href="#path-10"></use>
</mask>
<g id="SVGID_1_"></g>
<path d="M4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,4.80729167 C0,2.36666667 1.996875,0.369791667 4.4375,0.369791667 L29.5833333,0.369791667 L29.5833333,0 L4.4375,0 Z" id="Path" fill-opacity="0.2" fill="#FFFFFF" fill-rule="nonzero" mask="url(#mask-11)"></path>
</g>
<g id="Clipped">
<mask id="mask-13" fill="white">
<use xlink:href="#path-12"></use>
</mask>
<g id="SVGID_1_"></g>
<path d="M42.8958333,64.7135417 L4.4375,64.7135417 C1.996875,64.7135417 0,62.7166667 0,60.2760417 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,60.2760417 C47.3333333,62.7166667 45.3364583,64.7135417 42.8958333,64.7135417 Z" id="Path" fill-opacity="0.2" fill="#263238" fill-rule="nonzero" mask="url(#mask-13)"></path>
</g>
<g id="Clipped">
<mask id="mask-15" fill="white">
<use xlink:href="#path-14"></use>
</mask>
<g id="SVGID_1_"></g>
<path d="M34.0208333,17.75 C31.5691146,17.75 29.5833333,15.7642188 29.5833333,13.3125 L29.5833333,13.6822917 C29.5833333,16.1340104 31.5691146,18.1197917 34.0208333,18.1197917 L47.3333333,18.1197917 L47.3333333,17.75 L34.0208333,17.75 Z" id="Path" fill-opacity="0.1" fill="#263238" fill-rule="nonzero" mask="url(#mask-15)"></path>
</g>
</g>
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z" id="Path" fill="url(#radialGradient-16)" fill-rule="nonzero"></path>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@@ -0,0 +1,24 @@
import { IField, IGlobalVariable } from '@automatisch/types';
import { URLSearchParams } from 'url';
import authScope from '../common/auth-scope';
export default async function generateAuthUrl($: IGlobalVariable) {
const oauthRedirectUrlField = $.app.auth.fields.find(
(field: IField) => field.key == 'oAuthRedirectUrl'
);
const redirectUri = oauthRedirectUrlField.value as string;
const searchParams = new URLSearchParams({
client_id: $.auth.data.clientId as string,
redirect_uri: redirectUri,
prompt: 'select_account',
scope: authScope.join(' '),
response_type: 'code',
access_type: 'offline',
});
const url = `https://accounts.google.com/o/oauth2/v2/auth?${searchParams.toString()}`;
await $.auth.set({
url,
});
}

View File

@@ -0,0 +1,48 @@
import generateAuthUrl from './generate-auth-url';
import verifyCredentials from './verify-credentials';
import refreshToken from './refresh-token';
import isStillVerified from './is-still-verified';
export default {
fields: [
{
key: 'oAuthRedirectUrl',
label: 'OAuth Redirect URL',
type: 'string' as const,
required: true,
readOnly: true,
value: '{WEB_APP_URL}/app/google-sheets/connections/add',
placeholder: null,
description:
'When asked to input a redirect URL in Google Cloud, enter the URL above.',
clickToCopy: true,
},
{
key: 'clientId',
label: 'Client ID',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
clickToCopy: false,
},
{
key: 'clientSecret',
label: 'Client Secret',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
clickToCopy: false,
},
],
generateAuthUrl,
verifyCredentials,
isStillVerified,
refreshToken,
};

View File

@@ -0,0 +1,9 @@
import { IGlobalVariable } from '@automatisch/types';
import getCurrentUser from '../common/get-current-user';
const isStillVerified = async ($: IGlobalVariable) => {
const currentUser = await getCurrentUser($);
return !!currentUser.resourceName;
};
export default isStillVerified;

View File

@@ -0,0 +1,26 @@
import { URLSearchParams } from 'node:url';
import { IGlobalVariable } from '@automatisch/types';
import authScope from '../common/auth-scope';
const refreshToken = async ($: IGlobalVariable) => {
const params = new URLSearchParams({
client_id: $.auth.data.clientId as string,
client_secret: $.auth.data.clientSecret as string,
grant_type: 'refresh_token',
refresh_token: $.auth.data.refreshToken as string,
});
const { data } = await $.http.post(
'https://oauth2.googleapis.com/token',
params.toString()
);
await $.auth.set({
accessToken: data.access_token,
expiresIn: data.expires_in,
scope: authScope.join(' '),
tokenType: data.token_type,
});
};
export default refreshToken;

View File

@@ -0,0 +1,57 @@
import { IField, IGlobalVariable } from '@automatisch/types';
import getCurrentUser from '../common/get-current-user';
type TUser = {
displayName: string;
metadata: {
primary: boolean;
};
};
type TEmailAddress = {
value: string;
metadata: {
primary: boolean;
};
};
const verifyCredentials = async ($: IGlobalVariable) => {
const oauthRedirectUrlField = $.app.auth.fields.find(
(field: IField) => field.key == 'oAuthRedirectUrl'
);
const redirectUri = oauthRedirectUrlField.value as string;
const { data } = await $.http.post(`https://oauth2.googleapis.com/token`, {
client_id: $.auth.data.clientId,
client_secret: $.auth.data.clientSecret,
code: $.auth.data.code,
grant_type: 'authorization_code',
redirect_uri: redirectUri,
});
await $.auth.set({
accessToken: data.access_token,
tokenType: data.token_type,
});
const currentUser = await getCurrentUser($);
const { displayName } = currentUser.names.find(
(name: TUser) => name.metadata.primary
);
const { value: email } = currentUser.emailAddresses.find(
(emailAddress: TEmailAddress) => emailAddress.metadata.primary
);
await $.auth.set({
clientId: $.auth.data.clientId,
clientSecret: $.auth.data.clientSecret,
scope: $.auth.data.scope,
idToken: data.id_token,
expiresIn: data.expires_in,
refreshToken: data.refresh_token,
resourceName: currentUser.resourceName,
screenName: `${displayName} - ${email}`,
});
};
export default verifyCredentials;

View File

@@ -0,0 +1,11 @@
import { TBeforeRequest } from '@automatisch/types';
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
if ($.auth.data?.accessToken) {
requestConfig.headers.Authorization = `${$.auth.data.tokenType} ${$.auth.data.accessToken}`;
}
return requestConfig;
};
export default addAuthHeader;

View File

@@ -0,0 +1,8 @@
const authScope: string[] = [
'https://www.googleapis.com/auth/drive',
'https://www.googleapis.com/auth/spreadsheets',
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/userinfo.profile',
];
export default authScope;

View File

@@ -0,0 +1,10 @@
import { IGlobalVariable } from '@automatisch/types';
const getCurrentUser = async ($: IGlobalVariable) => {
const { data: currentUser } = await $.http.get(
'https://people.googleapis.com/v1/people/me?personFields=names,emailAddresses'
);
return currentUser;
};
export default getCurrentUser;

View File

@@ -0,0 +1,4 @@
import listDrives from './list-drives';
import listSpreadsheets from './list-spreadsheets';
export default [listDrives, listSpreadsheets];

View File

@@ -0,0 +1,38 @@
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
export default {
name: 'List drives',
key: 'listDrives',
async run($: IGlobalVariable) {
const drives: {
data: IJSONObject[];
} = {
data: [{ value: null, name: 'My Google Drive' }],
};
const params = {
pageSize: 100,
pageToken: undefined as unknown as string,
};
do {
const { data } = await $.http.get(
`https://www.googleapis.com/drive/v3/drives`,
{ params }
);
params.pageToken = data.nextPageToken;
if (data.drives) {
for (const drive of data.drives) {
drives.data.push({
value: drive.id,
name: drive.name,
});
}
}
} while (params.pageToken);
return drives;
},
};

View File

@@ -0,0 +1,46 @@
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
export default {
name: 'List spreadsheets',
key: 'listSpreadsheets',
async run($: IGlobalVariable) {
const spreadsheets: {
data: IJSONObject[];
} = {
data: [],
};
const params: Record<string, unknown> = {
q: `mimeType='application/vnd.google-apps.spreadsheet'`,
pageSize: 100,
pageToken: undefined as unknown as string,
orderBy: 'createdTime desc',
driveId: $.step.parameters.driveId,
supportsAllDrives: true,
};
if ($.step.parameters.driveId) {
params.includeItemsFromAllDrives = true;
}
do {
const { data } = await $.http.get(
`https://www.googleapis.com/drive/v3/files`,
{ params }
);
params.pageToken = data.nextPageToken;
if (data.files?.length) {
for (const file of data.files) {
spreadsheets.data.push({
value: file.id,
name: file.name,
});
}
}
} while (params.pageToken);
return spreadsheets;
},
};

View File

View File

@@ -0,0 +1,20 @@
import defineApp from '../../helpers/define-app';
import addAuthHeader from './common/add-auth-header';
import auth from './auth';
import triggers from './triggers';
import dynamicData from './dynamic-data';
export default defineApp({
name: 'Google Sheets',
key: 'google-sheets',
baseUrl: 'https://docs.google.com/spreadsheets',
apiBaseUrl: 'https://sheets.googleapis.com',
iconUrl: '{BASE_URL}/apps/google-sheets/assets/favicon.svg',
authDocUrl: 'https://automatisch.io/docs/apps/google-sheets/connection',
primaryColor: '0F9D58',
supportsConnections: true,
beforeRequest: [addAuthHeader],
auth,
triggers,
dynamicData,
});

View File

@@ -0,0 +1,4 @@
import newSpreadsheets from './new-spreadsheets';
import newWorksheets from './new-worksheets';
export default [newSpreadsheets, newWorksheets];

View File

@@ -0,0 +1,33 @@
import defineTrigger from '../../../../helpers/define-trigger';
import newSpreadsheets from './new-spreadsheets'
export default defineTrigger({
name: 'New Spreadsheets',
key: 'newSpreadsheets',
pollInterval: 15,
description: 'Triggers when you create a new spreadsheet.',
arguments: [
{
label: 'Drive',
key: 'driveId',
type: 'dropdown' as const,
required: false,
description: 'The Google Drive where your spreadsheet resides. If nothing is selected, then your personal Google Drive will be used.',
variables: false,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listDrives',
},
],
},
},
],
async run($) {
await newSpreadsheets($);
},
});

View File

@@ -0,0 +1,38 @@
import { IGlobalVariable } from '@automatisch/types';
const newSpreadsheets = async ($: IGlobalVariable) => {
const params: Record<string, unknown> = {
pageToken: undefined as unknown as string,
orderBy: 'createdTime desc',
q: `mimeType='application/vnd.google-apps.spreadsheet'`,
fields: '*',
pageSize: 1000,
driveId: $.step.parameters.driveId,
supportsAllDrives: true,
};
if ($.step.parameters.driveId) {
params.includeItemsFromAllDrives = true;
}
do {
const { data } = await $.http.get(
'https://www.googleapis.com/drive/v3/files',
{ params }
);
params.pageToken = data.nextPageToken;
if (data.files?.length) {
for (const file of data.files) {
$.pushTriggerItem({
raw: file,
meta: {
internalId: file.id,
},
});
}
}
} while (params.pageToken);
};
export default newSpreadsheets;

View File

@@ -0,0 +1,57 @@
import defineTrigger from '../../../../helpers/define-trigger';
import newWorksheets from './new-worksheets';
export default defineTrigger({
name: 'New Worksheets',
key: 'newWorksheets',
pollInterval: 15,
description: 'Triggers when you create a new worksheet in a spreadsheet.',
arguments: [
{
label: 'Drive',
key: 'driveId',
type: 'dropdown' as const,
required: false,
description:
'The Google Drive where your spreadsheet resides. If nothing is selected, then your personal Google Drive will be used.',
variables: false,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listDrives',
},
],
},
},
{
label: 'Spreadsheet',
key: 'spreadsheetId',
type: 'dropdown' as const,
required: true,
dependsOn: ['parameters.driveId'],
description: 'The spreadsheets in your Google Drive.',
variables: false,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listSpreadsheets',
},
{
name: 'parameters.driveId',
value: '{parameters.driveId}',
},
],
},
},
],
async run($) {
await newWorksheets($);
},
});

View File

@@ -0,0 +1,28 @@
import { IGlobalVariable } from '@automatisch/types';
const newWorksheets = async ($: IGlobalVariable) => {
const params = {
pageToken: undefined as unknown as string,
};
do {
const { data } = await $.http.get(
`/v4/spreadsheets/${$.step.parameters.spreadsheetId}`,
{ params }
);
params.pageToken = data.nextPageToken;
if (data.sheets?.length) {
for (const sheet of data.sheets.reverse()) {
$.pushTriggerItem({
raw: sheet,
meta: {
internalId: sheet.properties.sheetId.toString(),
},
});
}
}
} while (params.pageToken);
};
export default newWorksheets;

View File

@@ -1,7 +1,32 @@
import type { AxiosRequestConfig } from 'axios';
import defineAction from '../../../../helpers/define-action';
type TMethod = 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
type THeaderEntry = {
key: string;
value: string;
}
type THeaderEntries = THeaderEntry[];
function isPossiblyTextBased(contentType: string) {
if (!contentType) return false;
return contentType.startsWith('application/json')
|| contentType.startsWith('text/');
}
function throwIfFileSizeExceedsLimit(contentLength: string) {
const maxFileSize = 25 * 1024 * 1024; // 25MB
if (Number(contentLength) > maxFileSize) {
throw new Error(
`Response is too large. Maximum size is 25MB. Actual size is ${contentLength}`
);
}
}
export default defineAction({
name: 'Custom Request',
key: 'customRequest',
@@ -38,35 +63,87 @@ export default defineAction({
description: 'Place raw JSON data here.',
variables: true,
},
{
label: 'Headers',
key: 'headers',
type: 'dynamic' as const,
required: false,
description: 'Add or remove headers as needed',
value: [{
key: 'Content-Type',
value: 'application/json'
}],
fields: [
{
label: 'Key',
key: 'key',
type: 'string' as const,
required: true,
description: 'Header key',
variables: false,
},
{
label: 'Value',
key: 'value',
type: 'string' as const,
required: true,
description: 'Header value',
variables: true,
}
],
}
],
async run($) {
const method = $.step.parameters.method as TMethod;
const data = $.step.parameters.data as string;
const url = $.step.parameters.url as string;
const maxFileSize = 25 * 1024 * 1024; // 25MB
const headers = $.step.parameters.headers as THeaderEntries;
const metadataResponse = await $.http.head(url);
const headersObject: Record<string, string> = headers.reduce((result, entry) => {
const key = entry.key?.toLowerCase();
const value = entry.value;
if (Number(metadataResponse.headers['content-length']) > maxFileSize) {
throw new Error(
`Response is too large. Maximum size is 25MB. Actual size is ${metadataResponse.headers['content-length']}`
);
}
if (key && value) {
return {
...result,
[entry.key?.toLowerCase()]: entry.value
}
}
const response = await $.http.request({
return result;
}, {});
let contentType = headersObject['content-type'];
// in case HEAD request is not supported by the URL
try {
const metadataResponse = await $.http.head(url, { headers: headersObject });
contentType = metadataResponse.headers['content-type'];
throwIfFileSizeExceedsLimit(metadataResponse.headers['content-length']);
// eslint-disable-next-line no-empty
} catch { }
const requestData: AxiosRequestConfig = {
url,
method,
data,
headers: {
'Content-Type': 'application/json',
},
});
headers: headersObject,
};
if (!isPossiblyTextBased(contentType)) {
requestData.responseType = 'arraybuffer';
}
const response = await $.http.request(requestData);
throwIfFileSizeExceedsLimit(response.headers['content-length']);
let responseData = response.data;
if (typeof response.data === 'string') {
responseData = response.data.replaceAll('\u0000', '');
if (!isPossiblyTextBased(contentType)) {
responseData = Buffer.from(responseData as string).toString('base64');
}
$.setActionItem({ raw: { data: responseData } });

View File

@@ -1,8 +1,8 @@
import { TBeforeRequest } from '@automatisch/types';
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
if ($.auth.data.apiBaseUrl) {
requestConfig.baseURL = $.auth.data.apiBaseUrl as string;
if ($.auth.data.serverUrl) {
requestConfig.baseURL = $.auth.data.serverUrl as string;
}
if ($.auth.data?.username && $.auth.data?.password) {

View File

@@ -1,4 +1,5 @@
import checkModeration from './check-moderation';
import sendPrompt from './send-prompt';
import sendChatPrompt from './send-chat-prompt';
export default [checkModeration, sendPrompt];
export default [checkModeration, sendChatPrompt, sendPrompt];

View File

@@ -0,0 +1,137 @@
import defineAction from '../../../../helpers/define-action';
type TMessage = {
role: string;
content: string;
}
const castFloatOrUndefined = (value: string | null) => {
return value === '' ? undefined : parseFloat(value);
}
export default defineAction({
name: 'Send chat prompt',
key: 'sendChatPrompt',
description: 'Creates a completion for the provided prompt and parameters.',
arguments: [
{
label: 'Model',
key: 'model',
type: 'dropdown' as const,
required: true,
variables: false,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listModels',
},
],
},
},
{
label: 'Messages',
key: 'messages',
type: 'dynamic' as const,
required: false,
description: 'Add or remove messages as needed',
value: [{ role: 'system', body: '' }],
fields: [
{
label: 'Role',
key: 'role',
type: 'dropdown' as const,
required: true,
options: [
{
label: 'System',
value: 'system',
},
{
label: 'User',
value: 'user',
}
],
},
{
label: 'Content',
key: 'content',
type: 'string' as const,
required: true,
variables: true,
}
],
},
{
label: 'Temperature',
key: 'temperature',
type: 'string' as const,
required: false,
variables: true,
description: 'What sampling temperature to use. Higher values mean the model will take more risk. Try 0.9 for more creative applications, and 0 for ones with a well-defined answer. We generally recommend altering this or Top P but not both.'
},
{
label: 'Maximum tokens',
key: 'maxTokens',
type: 'string' as const,
required: false,
variables: true,
description: 'The maximum number of tokens to generate in the completion.'
},
{
label: 'Stop Sequence',
key: 'stopSequence',
type: 'string' as const,
required: false,
variables: true,
description: 'Single stop sequence where the API will stop generating further tokens. The returned text will not contain the stop sequence.'
},
{
label: 'Top P',
key: 'topP',
type: 'string' as const,
required: false,
variables: true,
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.'
},
{
label: 'Frequency Penalty',
key: 'frequencyPenalty',
type: 'string' as const,
required: false,
variables: true,
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.`
},
{
label: 'presencePenalty',
key: 'presencePenalty',
type: 'string' as const,
required: false,
variables: true,
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.`
},
],
async run($) {
const payload = {
model: $.step.parameters.model as string,
temperature: castFloatOrUndefined($.step.parameters.temperature as string),
max_tokens: castFloatOrUndefined($.step.parameters.maxTokens as string),
stop: ($.step.parameters.stopSequence as string || null),
top_p: castFloatOrUndefined($.step.parameters.topP as string),
frequency_penalty: castFloatOrUndefined($.step.parameters.frequencyPenalty as string),
presence_penalty: castFloatOrUndefined($.step.parameters.presencePenalty as string),
messages: ($.step.parameters.messages as TMessage[]).map(message => ({
role: message.role,
content: message.content,
})),
};
const { data } = await $.http.post('/v1/chat/completions', payload);
$.setActionItem({
raw: data,
});
},
});

View File

@@ -0,0 +1,111 @@
import { IJSONArray } from '@automatisch/types';
import defineAction from '../../../../helpers/define-action';
import getClient from '../../common/postgres-client';
import setParams from '../../common/set-run-time-parameters';
import whereClauseOperators from '../../common/where-clause-operators';
type TWhereClauseEntry = { columnName: string, value: string, operator: string };
type TWhereClauseEntries = TWhereClauseEntry[];
export default defineAction({
name: 'Delete',
key: 'delete',
description: 'Delete rows found based on the given where clause entries.',
arguments: [
{
label: 'Schema name',
key: 'schema',
type: 'string' as const,
value: 'public',
required: true,
variables: false,
},
{
label: 'Table name',
key: 'table',
type: 'string' as const,
required: true,
variables: false,
},
{
label: 'Where clause entries',
key: 'whereClauseEntries',
type: 'dynamic' as const,
required: true,
fields: [
{
label: 'Column name',
key: 'columnName',
type: 'string' as const,
required: true,
variables: false,
},
{
label: 'Operator',
key: 'operator',
type: 'dropdown' as const,
required: true,
variables: false,
options: whereClauseOperators
},
{
label: 'Value',
key: 'value',
type: 'string' as const,
required: true,
variables: true,
}
]
},
{
label: 'Run-time parameters',
key: 'params',
type: 'dynamic' as const,
required: false,
description: 'Change run-time configuration parameters with SET command',
fields: [
{
label: 'Parameter name',
key: 'parameter',
type: 'string' as const,
required: true,
variables: false,
},
{
label: 'Value',
key: 'value',
type: 'string' as const,
required: true,
variables: true,
}
],
}
],
async run($) {
const client = getClient($);
await setParams(client, $.step.parameters.params);
const whereClauseEntries = $.step.parameters.whereClauseEntries as TWhereClauseEntries;
const response = await client($.step.parameters.table as string)
.withSchema($.step.parameters.schema as string)
.returning('*')
.where((builder) => {
for (const whereClauseEntry of whereClauseEntries) {
const { columnName, operator, value } = whereClauseEntry;
if (columnName) {
builder.where(columnName, operator, value);
}
}
})
.del() as IJSONArray;
$.setActionItem({
raw: {
rows: response
}
});
},
});

View File

@@ -0,0 +1,6 @@
import insertAction from './insert';
import updateAction from './update';
import deleteAction from './delete';
import SQLQuery from './sql-query'
export default [insertAction, updateAction, deleteAction, SQLQuery];

View File

@@ -0,0 +1,93 @@
import { IJSONObject } from '@automatisch/types';
import defineAction from '../../../../helpers/define-action';
import getClient from '../../common/postgres-client';
import setParams from '../../common/set-run-time-parameters';
type TColumnValueEntries = { columnName: string, value: string }[];
export default defineAction({
name: 'Insert',
key: 'insert',
description: 'Create a new row in a table in specified schema.',
arguments: [
{
label: 'Schema name',
key: 'schema',
type: 'string' as const,
value: 'public',
required: true,
variables: false,
},
{
label: 'Table name',
key: 'table',
type: 'string' as const,
required: true,
variables: false,
},
{
label: 'Column - value entries',
key: 'columnValueEntries',
type: 'dynamic' as const,
required: true,
description: 'Table columns with values',
fields: [
{
label: 'Column name',
key: 'columnName',
type: 'string' as const,
required: true,
variables: false,
},
{
label: 'Value',
key: 'value',
type: 'string' as const,
required: true,
variables: true,
}
],
},
{
label: 'Run-time parameters',
key: 'params',
type: 'dynamic' as const,
required: false,
description: 'Change run-time configuration parameters with SET command',
fields: [
{
label: 'Parameter name',
key: 'parameter',
type: 'string' as const,
required: true,
variables: false,
},
{
label: 'Value',
key: 'value',
type: 'string' as const,
required: true,
variables: true,
}
],
}
],
async run($) {
const client = getClient($);
await setParams(client, $.step.parameters.params);
const fields = $.step.parameters.columnValueEntries as TColumnValueEntries;
const data = fields.reduce((result, { columnName, value }) => ({
...result,
[columnName]: value,
}), {});
const response = await client($.step.parameters.table as string)
.withSchema($.step.parameters.schema as string)
.returning('*')
.insert(data) as IJSONObject;
$.setActionItem({ raw: response[0] as IJSONObject });
},
});

View File

@@ -0,0 +1,56 @@
import defineAction from '../../../../helpers/define-action';
import getClient from '../../common/postgres-client';
import setParams from '../../common/set-run-time-parameters';
export default defineAction({
name: 'SQL query',
key: 'SQLQuery',
description: 'Executes the given SQL statement.',
arguments: [
{
label: 'SQL statement',
key: 'queryStatement',
type: 'string' as const,
value: 'public',
required: true,
variables: true,
},
{
label: 'Run-time parameters',
key: 'params',
type: 'dynamic' as const,
required: false,
description: 'Change run-time configuration parameters with SET command',
fields: [
{
label: 'Parameter name',
key: 'parameter',
type: 'string' as const,
required: true,
variables: false,
},
{
label: 'Value',
key: 'value',
type: 'string' as const,
required: true,
variables: true,
}
],
}
],
async run($) {
const client = getClient($);
await setParams(client, $.step.parameters.params);
const queryStatemnt = $.step.parameters.queryStatement;
const { rows } = await client.raw(queryStatemnt);
$.setActionItem({
raw: {
rows
}
});
},
});

View File

@@ -0,0 +1,141 @@
import { IJSONArray } from '@automatisch/types';
import defineAction from '../../../../helpers/define-action';
import getClient from '../../common/postgres-client';
import setParams from '../../common/set-run-time-parameters';
import whereClauseOperators from '../../common/where-clause-operators';
type TColumnValueEntries = { columnName: string, value: string }[];
type TWhereClauseEntry = { columnName: string, value: string, operator: string };
type TWhereClauseEntries = TWhereClauseEntry[];
export default defineAction({
name: 'Update',
key: 'update',
description: 'Update rows found based on the given where clause entries.',
arguments: [
{
label: 'Schema name',
key: 'schema',
type: 'string' as const,
value: 'public',
required: true,
variables: false,
},
{
label: 'Table name',
key: 'table',
type: 'string' as const,
required: true,
variables: false,
},
{
label: 'Where clause entries',
key: 'whereClauseEntries',
type: 'dynamic' as const,
required: true,
fields: [
{
label: 'Column name',
key: 'columnName',
type: 'string' as const,
required: true,
variables: false,
},
{
label: 'Operator',
key: 'operator',
type: 'dropdown' as const,
required: true,
variables: false,
options: whereClauseOperators
},
{
label: 'Value',
key: 'value',
type: 'string' as const,
required: true,
variables: true,
}
]
},
{
label: 'Column - value entries',
key: 'columnValueEntries',
type: 'dynamic' as const,
required: true,
description: 'Table columns with values',
fields: [
{
label: 'Column name',
key: 'columnName',
type: 'string' as const,
required: true,
variables: false,
},
{
label: 'Value',
key: 'value',
type: 'string' as const,
required: true,
variables: true,
}
],
},
{
label: 'Run-time parameters',
key: 'params',
type: 'dynamic' as const,
required: false,
description: 'Change run-time configuration parameters with SET command',
fields: [
{
label: 'Parameter name',
key: 'parameter',
type: 'string' as const,
required: true,
variables: false,
},
{
label: 'Value',
key: 'value',
type: 'string' as const,
required: true,
variables: true,
}
],
}
],
async run($) {
const client = getClient($);
await setParams(client, $.step.parameters.params);
const whereClauseEntries = $.step.parameters.whereClauseEntries as TWhereClauseEntries;
const fields = $.step.parameters.columnValueEntries as TColumnValueEntries;
const data: Record<string, unknown> = fields.reduce((result, { columnName, value }) => ({
...result,
[columnName]: value,
}), {});
const response = await client($.step.parameters.table as string)
.withSchema($.step.parameters.schema as string)
.returning('*')
.where((builder) => {
for (const whereClauseEntry of whereClauseEntries) {
const { columnName, operator, value } = whereClauseEntry;
if (columnName) {
builder.where(columnName, operator, value);
}
}
})
.update(data) as IJSONArray;
$.setActionItem({
raw: {
rows: response
}
});
},
});

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -0,0 +1,98 @@
import verifyCredentials from './verify-credentials';
import isStillVerified from './is-still-verified';
export default {
fields: [
{
key: 'version',
label: 'PostgreSQL version',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description:
'The version of PostgreSQL database that user want to connect with.',
clickToCopy: false,
},
{
key: 'host',
label: 'Host',
type: 'string' as const,
required: true,
readOnly: false,
value: '127.0.0.1',
placeholder: null,
description: 'The host of the PostgreSQL database.',
clickToCopy: false,
},
{
key: 'port',
label: 'Port',
type: 'string' as const,
required: true,
readOnly: false,
value: '5432',
placeholder: null,
description: 'The port of the PostgreSQL database.',
clickToCopy: false,
},
{
key: 'enableSsl',
label: 'Enable SSL',
type: 'dropdown' as const,
required: true,
readOnly: false,
value: 'false',
description: 'The port of the PostgreSQL database.',
variables: false,
clickToCopy: false,
options: [
{
label: 'True',
value: 'true',
},
{
label: 'False',
value: 'false',
},
],
},
{
key: 'database',
label: 'Database name',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description: 'The database name of the PostgreSQL database.',
clickToCopy: false,
},
{
key: 'user',
label: 'Database username',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description: 'The user who has access on postgres database.',
clickToCopy: false,
},
{
key: 'password',
label: 'Password',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description: 'The password of the PostgreSQL database user.',
clickToCopy: false,
},
],
verifyCredentials,
isStillVerified,
};

View File

@@ -0,0 +1,10 @@
import { IGlobalVariable } from '@automatisch/types';
import verifyCredentials from './verify-credentials';
const isStillVerified = async ($: IGlobalVariable) => {
await verifyCredentials($);
return true;
};
export default isStillVerified;

View File

@@ -0,0 +1,25 @@
import { IGlobalVariable } from '@automatisch/types';
import logger from '../../../helpers/logger';
import getClient from '../common/postgres-client';
const verifyCredentials = async ($: IGlobalVariable) => {
const client = getClient($);
const checkConnection = await client.raw('SELECT 1');
logger.debug(checkConnection);
await $.auth.set({
screenName: `${$.auth.data.user}@${$.auth.data.host}:${$.auth.data.port}/${$.auth.data.database}`,
client: 'pg',
version: $.auth.data.version,
host: $.auth.data.host,
port: Number($.auth.data.port),
enableSsl:
$.auth.data.enableSsl === 'true' || $.auth.data.enableSsl === true,
user: $.auth.data.user,
password: $.auth.data.password,
database: $.auth.data.database,
});
};
export default verifyCredentials;

View File

@@ -0,0 +1,22 @@
import knex, { Knex } from 'knex';
import { IGlobalVariable } from '@automatisch/types';
const getClient = ($: IGlobalVariable): Knex<any, unknown[]> => {
const client = knex({
client: 'pg',
version: $.auth.data.version as string,
connection: {
host: $.auth.data.host as string,
port: Number($.auth.data.port),
ssl: ($.auth.data.enableSsl === 'true' ||
$.auth.data.enableSsl === true) as boolean,
user: $.auth.data.user as string,
password: $.auth.data.password as string,
database: $.auth.data.database as string,
},
});
return client;
};
export default getClient;

View File

@@ -0,0 +1,19 @@
import { Knex } from 'knex';
import { type IJSONValue } from '@automatisch/types';
type TParams = { parameter: string; value: string; }[];
const setParams = async (client: Knex<any, unknown[]>, params: IJSONValue = []): Promise<void> => {
for (const { parameter, value } of (params as TParams)) {
if (parameter) {
const bindings = {
parameter,
value,
};
await client.raw('SET :parameter: = :value:', bindings);
}
}
};
export default setParams;

View File

@@ -0,0 +1,60 @@
const whereClauseOperators = [
{
value: "=",
label: "="
},
{
value: ">",
label: ">"
},
{
value: "<",
label: "<"
},
{
value: ">=",
label: ">="
},
{
value: "<=",
label: "<="
},
{
value: "<>",
label: "<>"
},
{
value: "!=",
label: "!="
},
{
value: "AND",
label: "AND"
},
{
value: "OR",
label: "OR"
},
{
value: "IN",
label: "IN"
},
{
value: "BETWEEN",
label: "BETWEEN"
},
{
value: "LIKE",
label: "LIKE"
},
{
value: "IS NULL",
label: "IS NULL"
},
{
value: "NOT",
label: "NOT"
}
];
export default whereClauseOperators;

View File

View File

@@ -0,0 +1,16 @@
import defineApp from '../../helpers/define-app';
import auth from './auth';
import actions from './actions';
export default defineApp({
name: 'PostgreSQL',
key: 'postgresql',
iconUrl: '{BASE_URL}/apps/postgresql/assets/favicon.svg',
authDocUrl: 'https://automatisch.io/docs/apps/postgresql/connection',
supportsConnections: true,
baseUrl: '',
apiBaseUrl: '',
primaryColor: '336791',
auth,
actions,
});

View File

@@ -0,0 +1,3 @@
import sendSms from './send-sms';
export default [sendSms];

View File

@@ -0,0 +1,63 @@
import defineAction from '../../../../helpers/define-action';
export default defineAction({
name: 'Send an SMS',
key: 'sendSms',
description: 'Sends an SMS',
arguments: [
{
label: 'From Number',
key: 'fromNumber',
type: 'dropdown' as const,
required: true,
description:
'The number to send the SMS from. Include only country code. Example: 491234567890',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listIncomingPhoneNumbers',
},
],
},
},
{
label: 'To Number',
key: 'toNumber',
type: 'string' as const,
required: true,
description:
'The number to send the SMS to. Include only country code. Example: 491234567890',
variables: true,
},
{
label: 'Message',
key: 'message',
type: 'string' as const,
required: true,
description: 'The content of the message.',
variables: true,
},
],
async run($) {
const requestPath = `/api/laml/2010-04-01/Accounts/${$.auth.data.accountSid}/Messages`;
const Body = $.step.parameters.message;
const From = $.step.parameters.fromNumber;
const To = '+' + ($.step.parameters.toNumber as string).trim();
const response = await $.http.post(requestPath, null, {
params: {
Body,
From,
To,
}
});
$.setActionItem({ raw: response.data });
},
});

View File

@@ -0,0 +1 @@
<svg id="a1100050-5390-497e-a7fa-2bb69ec95c7c" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 318.33 362.7"><defs><style>.a463a3b0-e95c-44d7-b809-5997966784eb{fill:#044ef4;}.b59fade4-5c2c-49f1-a6bf-917d1f195a19{fill:#f72a72;}</style></defs><path class="a463a3b0-e95c-44d7-b809-5997966784eb" d="M389.17,278c0,10.31-2.8,17.06-8.37,22.62q-50.07,49.95-100.19,99.85C269,412,252.06,412.54,240.55,402c-12.12-11.13-12.6-29.39-.75-41.47,15-15.34,30.32-30.47,45.56-45.63q27.47-27.3,55.06-54.45c8.91-8.75,20.84-11.07,31.77-6.1C383.3,259.36,388.79,268.25,389.17,278Z" transform="translate(-70.83 -46.81)"/><path class="a463a3b0-e95c-44d7-b809-5997966784eb" d="M70.84,172.94c.16-5.21,2.93-11.81,8.36-17.24q49.89-49.77,99.8-99.53c6.92-6.91,15.08-10.45,24.83-9.06,11.55,1.65,19.62,8.12,23.38,19.22s1.16,21.14-7,29.43q-23.87,24.21-48,48.1-26.22,26.07-52.58,52c-8.93,8.76-20.84,10.92-31.8,6.13C77.17,197.35,70.65,187.14,70.84,172.94Z" transform="translate(-70.83 -46.81)"/><path class="b59fade4-5c2c-49f1-a6bf-917d1f195a19" d="M93.68,210.69c3.79-.17,6.91-.08,10-.49a34.39,34.39,0,0,0,20.56-10.34c6.38-6.52,12.79-13,19.33-19.66,1.23,1.09,2,1.7,2.66,2.38q36.92,36.9,73.81,73.83c8.07,8.1,10.9,17.87,7.66,28.86-3.12,10.58-10.39,17.35-21.17,19.77-9.33,2.1-18.23.31-25.07-6.47C152.25,269.64,123.32,240.42,93.68,210.69Z" transform="translate(-70.83 -46.81)"/><path class="b59fade4-5c2c-49f1-a6bf-917d1f195a19" d="M366.57,246c-15-1.53-25.7,4.26-34.72,14.22-4.89,5.4-10.23,10.39-15.51,15.69-1.1-1-1.86-1.59-2.56-2.28q-36.94-36.93-73.86-73.89c-8.07-8.1-10.89-17.89-7.56-28.89,3.19-10.56,10.47-17.31,21.28-19.68,9.56-2.1,18.45,0,25.44,6.92q43,42.57,85.61,85.47C365.12,244,365.44,244.54,366.57,246Z" transform="translate(-70.83 -46.81)"/></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

Some files were not shown because too many files have changed in this diff Show More