Compare commits

..

324 Commits

Author SHA1 Message Date
Ali BARIN
5c78e6e7bf Release v0.2.0 2022-11-12 16:21:42 +01:00
Ömer Faruk Aydın
e8b06e9b78 Merge pull request #711 from automatisch/fix/wait-for-postgres-worker
fix: Wait for postgres in worker service
2022-11-12 16:17:33 +01:00
Faruk AYDIN
d67db9c63e fix: Wait for postgres in worker service 2022-11-12 16:15:06 +01:00
Faruk AYDIN
0dfdbc809c docs: Adjust the functionality of build integration 2022-11-11 09:54:05 +01:00
Faruk AYDIN
adddf23ae3 refactor: Check http error for actions 2022-11-11 09:53:53 +01:00
Ömer Faruk Aydın
eac3fd24a1 Merge pull request #708 from automatisch/docs/dynamic-data-explanation
docs: Change data to dynamic data for folder structure
2022-11-10 21:13:24 +01:00
Faruk AYDIN
599c0f8cfc docs: Change data to dynamic data for folder structure 2022-11-10 20:53:44 +01:00
Ömer Faruk Aydın
b1ec8467a6 Merge pull request #707 from automatisch/refactor/dynamic-data
refactor: Use getDynamicData instead of getData
2022-11-10 19:48:30 +01:00
Faruk AYDIN
d2b8d2beff refactor: Use getDynamicData instead of getData 2022-11-10 19:46:04 +01:00
Ömer Faruk Aydın
a415c7ef4e Merge pull request #706 from automatisch/docs/auth-fields
docs: Add $.app.auth.fields to global variable
2022-11-10 19:28:28 +01:00
Faruk AYDIN
1faf86a9cb docs: Add $.app.auth.fields to global variable 2022-11-10 19:22:18 +01:00
Ömer Faruk Aydın
3b7d2e9238 Merge pull request #705 from automatisch/docs/generate-auth-url
docs: Add tip info for generateAuthUrl method
2022-11-10 19:18:50 +01:00
Faruk AYDIN
829d0cafe9 docs: Add tip info for generateAuthUrl method 2022-11-10 19:16:06 +01:00
Ömer Faruk Aydın
a103ca9747 Merge pull request #704 from automatisch/expand-auth-arguments
feat: add .all support in auth arguments
2022-11-10 18:56:40 +01:00
Faruk AYDIN
81c41ef5ee refactor: Use generateAuthUrl method instead of createAuthData 2022-11-10 18:50:36 +01:00
Faruk AYDIN
10e2794ff9 refactor: Generate authentication steps from auth properties 2022-11-10 18:49:49 +01:00
Ali BARIN
6d3606abf0 refactor(twitter): use .all in auth args 2022-11-10 00:12:26 +01:00
Ali BARIN
bbace2b2c1 feat: add .all support in auth arguments 2022-11-10 00:12:09 +01:00
Ömer Faruk Aydın
5aa937f56a Merge pull request #703 from automatisch/docs/build-integrations
Docs/build integrations
2022-11-09 23:39:01 +01:00
Faruk AYDIN
44159af83e docs: Expands the explanation of already procesed control 2022-11-09 23:36:36 +01:00
Faruk AYDIN
98041b7af2 docs: Explain $.auth.set expands the value rather than overriding 2022-11-09 23:32:57 +01:00
Faruk AYDIN
72e1fe3c21 docs: Explain that trigger array order will be reflected on UI 2022-11-09 23:29:21 +01:00
Ömer Faruk Aydın
af8d1190c5 docs: Fix wording for the global variable page
Co-authored-by: Ali BARIN <ali.barin53@gmail.com>
2022-11-09 23:10:56 +01:00
Ömer Faruk Aydın
b07e564a23 Merge pull request #702 from automatisch/issue-663
feat: add circular progress in flow step
2022-11-09 22:45:31 +01:00
Faruk AYDIN
4b8ac0ff9c docs: Add explanation to include screenName in verify credentials 2022-11-09 22:42:51 +01:00
Faruk AYDIN
0d479426ed docs: Add $.step.parameters property to global variable 2022-11-09 22:42:51 +01:00
Faruk AYDIN
e956b2d959 docs: Separate the tips of pushTriggerItem method 2022-11-09 22:42:51 +01:00
Faruk AYDIN
580e97ffe3 docs: Adjust wording for building triggers and actions 2022-11-09 22:42:51 +01:00
Faruk AYDIN
1cb951056a docs: Fix grammar mistakes for build integrations 2022-11-09 22:42:51 +01:00
Faruk AYDIN
a973887db2 docs: Add explanation for not supporting webhook based triggers atm 2022-11-09 22:42:51 +01:00
Faruk AYDIN
86ae81526a docs: Fix the wording for actions of build integrations 2022-11-09 22:42:51 +01:00
Faruk AYDIN
9d62a552fe docs: Add explanation of setActionItem usage 2022-11-09 22:42:51 +01:00
Faruk AYDIN
5497084b57 docs: Add missing properties of global variable 2022-11-09 22:42:51 +01:00
Faruk AYDIN
39b7effc26 docs: Use tip box for the integration and app explanation 2022-11-09 22:42:51 +01:00
Faruk AYDIN
2c847f62af fix: Cat svg path problem for docs 2022-11-09 22:42:51 +01:00
Faruk AYDIN
01ac26de8c docs: Add warning to visit development setup page for integrations 2022-11-09 22:42:51 +01:00
Faruk AYDIN
c579eebd16 docs: Add content table for build integration pages 2022-11-09 22:42:51 +01:00
Faruk AYDIN
47afd60ddf docs: Add warning to say integration and app used interchangeably 2022-11-09 22:42:51 +01:00
Faruk AYDIN
f432e72253 docs: Add actions page to build integration section 2022-11-09 22:42:51 +01:00
Faruk AYDIN
d572623552 docs: Add examples page for build integrations 2022-11-09 22:42:51 +01:00
Faruk AYDIN
3a129557e0 docs: Add triggers page to build integrations guide 2022-11-09 22:42:51 +01:00
Faruk AYDIN
49fac8f147 docs: Add how to test authentication part to auth 2022-11-09 22:42:51 +01:00
Faruk AYDIN
3140fb2435 docs: Add auth page to build integration guide 2022-11-09 22:42:51 +01:00
Faruk AYDIN
22cea145f8 docs: Add global variable page 2022-11-09 22:42:51 +01:00
Ali BARIN
6c93d46ec1 feat: add circular progress in flow step 2022-11-09 00:47:04 +01:00
Ömer Faruk Aydın
3c73064b19 Merge pull request #700 from automatisch/dependabot/npm_and_yarn/loader-utils-1.4.1
chore(deps): bump loader-utils from 1.4.0 to 1.4.1
2022-11-08 23:57:17 +01:00
Ömer Faruk Aydın
247ac1f96f Merge branch 'main' into dependabot/npm_and_yarn/loader-utils-1.4.1 2022-11-08 23:34:46 +01:00
Ömer Faruk Aydın
642f32ab4e Merge pull request #701 from automatisch/fix-e2e-tests
test: replace Slack with DeepL
2022-11-08 23:34:22 +01:00
Ömer Faruk Aydın
41cb1c71c7 Merge pull request #699 from automatisch/issue-694
feat(web): add status in execution row
2022-11-08 23:33:58 +01:00
Ömer Faruk Aydın
41de2aa769 Merge pull request #698 from automatisch/issue-695
feat(web): hide empty error tab in execution step
2022-11-08 23:30:48 +01:00
Ömer Faruk Aydın
e9e7b679f4 Merge pull request #697 from automatisch/refactor-is-still-verified
refactor: abstract exception capture out of is-still-verified
2022-11-08 23:09:43 +01:00
Ali BARIN
c9022f4208 test: do not display empty error in execution step 2022-11-08 21:58:27 +01:00
Ali BARIN
0a4877993d test: replace Slack with DeepL 2022-11-08 21:58:02 +01:00
dependabot[bot]
ca74fa626a chore(deps): bump loader-utils from 1.4.0 to 1.4.1
Bumps [loader-utils](https://github.com/webpack/loader-utils) from 1.4.0 to 1.4.1.
- [Release notes](https://github.com/webpack/loader-utils/releases)
- [Changelog](https://github.com/webpack/loader-utils/blob/v1.4.1/CHANGELOG.md)
- [Commits](https://github.com/webpack/loader-utils/compare/v1.4.0...v1.4.1)

---
updated-dependencies:
- dependency-name: loader-utils
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-08 20:36:16 +00:00
Ali BARIN
608038cbcc feat(web): add status in execution row 2022-11-08 21:04:56 +01:00
Ali BARIN
bf009ae13b chore(types): add status in Execution 2022-11-08 21:04:23 +01:00
Ali BARIN
bdb7a1b840 feat(web): hide empty error tab in execution step 2022-11-08 20:02:14 +01:00
Ali BARIN
d2b0a57064 refactor: abstract exception capture out of is-still-verified 2022-11-08 19:48:54 +01:00
Ali BARIN
dbceed563a feat: eager load apps 2022-11-08 00:49:55 +01:00
Ömer Faruk Aydın
cbf1d86c26 Merge pull request #688 from automatisch/revise-custom-errors
Revise custom errors
2022-11-07 22:56:31 +01:00
Faruk AYDIN
06a2014bc1 feat: Enrich error responses 2022-11-07 22:51:30 +01:00
Ali BARIN
ad46b3eea3 refactor: adapt errors in apps 2022-11-07 20:31:52 +01:00
Ali BARIN
5df7867b6d refactor(BaseError): leverage error.message 2022-11-07 20:31:52 +01:00
Ali BARIN
ceea962464 fix(AddAppConnection): word break error messages 2022-11-07 20:31:52 +01:00
Ömer Faruk Aydın
72386bc589 Merge pull request #693 from automatisch/chore/deepl-auth-field-typo
fix: Typo of DeepL authentication key field
2022-11-07 19:13:23 +01:00
Faruk AYDIN
34068a8188 fix: Typo of DeepL authentication key field 2022-11-07 19:03:17 +01:00
Ömer Faruk Aydın
414d521588 Merge pull request #692 from automatisch/docs/deepl-integration
docs: Add actions and connection pages for DeepL
2022-11-07 19:01:28 +01:00
Faruk AYDIN
d15e3d4c36 docs: Add actions and connection pages for DeepL 2022-11-07 18:58:45 +01:00
Ömer Faruk Aydın
81b850c1f7 Merge pull request #691 from automatisch/feature/deepl-integration
feat: Add DeepL integration with translate text action
2022-11-07 18:55:53 +01:00
Faruk AYDIN
527dfe6971 feat: Add DeepL integration with translate text action 2022-11-07 18:46:51 +01:00
Faruk AYDIN
d681d06de1 docs: Add salesforce to available apps 2022-11-07 09:55:33 +01:00
Ömer Faruk Aydın
9fe8ef0238 Merge pull request #689 from automatisch/describe-sf-auth
docs(salesforce): describe auth and triggers
2022-11-06 22:27:25 +01:00
Ali BARIN
99640b3d73 docs(salesforce): describe auth and triggers 2022-11-06 21:30:05 +01:00
Ömer Faruk Aydın
c9925ba8fe Merge pull request #687 from automatisch/docs/app-definition
docs: Add app definition page for build integrations
2022-11-06 11:26:09 +01:00
Faruk AYDIN
3b829af43d docs: Add app definition page for build integrations 2022-11-06 02:03:37 +01:00
Ömer Faruk Aydın
2abe23f918 Merge pull request #686 from automatisch/lint
Auto format whole project
2022-11-06 00:06:05 +01:00
Ali BARIN
475f24f661 style: auto format whole project 2022-11-05 23:57:33 +01:00
Ali BARIN
e338770e57 chore: add prettier as default formatter 2022-11-05 22:44:43 +01:00
Ömer Faruk Aydın
6bb2a583de Merge pull request #685 from automatisch/refactor/use-app-key
refactor: Use app key instead of name for new connection url
2022-11-05 22:33:37 +01:00
Faruk AYDIN
74bbbf55f8 refactor: Use app key instead of name for new connection url 2022-11-05 21:34:58 +01:00
Ömer Faruk Aydın
20b87cb000 Merge pull request #684 from automatisch/chore/before-request
chore: Make beforeRequest field optional for IApp
2022-11-05 21:31:43 +01:00
Faruk AYDIN
a3d82c5d70 chore: Make beforeRequest field optional for IApp 2022-11-05 21:29:07 +01:00
Ömer Faruk Aydın
c778c0c111 Merge pull request #683 from automatisch/docs/folder-structure
docs: Add folder structure page to build integrations section
2022-11-05 20:42:06 +01:00
Faruk AYDIN
bb7ef994f7 docs: Add folder structure page to build integrations section 2022-11-05 20:12:40 +01:00
Ömer Faruk Aydın
278b1ed7c0 Merge pull request #682 from automatisch/salesforce/replace-object-with-record
refactor(salesforce): replace object with record
2022-11-05 16:13:17 +01:00
Ali BARIN
08bfb953de refactor(salesforce): replace object with record 2022-11-05 15:33:08 +01:00
Ömer Faruk Aydın
276b1ef4cd Merge pull request #681 from automatisch/salesforce/updated-field-in-object
feat(salesforce): add updated field in objects trigger
2022-11-05 15:02:14 +01:00
Faruk AYDIN
cbd408ae89 fix(salesforce): Implement guard for list objects 2022-11-05 14:53:38 +01:00
Ali BARIN
fc6822a298 feat(salesforce): add updated field in objects trigger 2022-11-05 02:43:14 +01:00
Ali BARIN
43b7e506a7 fix(web): add missing options in GetApps query 2022-11-05 02:42:46 +01:00
Ali BARIN
f11a4b8472 fix(TestSubstep): don't show length of empty errors 2022-11-05 02:41:55 +01:00
Ali BARIN
927ceb6768 feat(salesforce): add list fields data 2022-11-05 02:41:12 +01:00
Ali BARIN
3d9249192c feat(salesforce): add list objects data 2022-11-05 02:40:46 +01:00
Ömer Faruk Aydın
243e50465f Merge pull request #680 from automatisch/chore/use-definitive-article
chore: Use definitive article for the license
2022-11-05 00:53:32 +01:00
Faruk AYDIN
d539bc6fc8 chore: Use definitive article for the license 2022-11-05 00:41:38 +01:00
Ömer Faruk Aydın
6fc52c8b4c Merge pull request #679 from automatisch/docs/contributing-section
docs: Add contributing sections to homepage
2022-11-04 23:52:38 +01:00
Ömer Faruk Aydın
df3bf4ce11 Merge pull request #678 from automatisch/fix/overflow-docs
fix: Add overflow-y scroll to root css of docs
2022-11-04 23:52:25 +01:00
Faruk AYDIN
c35753c63e docs: Add contributing sections to homepage 2022-11-04 23:47:51 +01:00
Faruk AYDIN
8d2cac5018 fix: Add overflow-y scroll to root css of docs 2022-11-04 23:46:59 +01:00
Ömer Faruk Aydın
a8c8efb4bd Merge pull request #676 from automatisch/salesforce/auth
feat(salesforce): add authentication
2022-11-04 01:24:21 +01:00
Ömer Faruk Aydın
c94b52aa61 Merge pull request #677 from automatisch/chore/change-auth-doc-url-of-apps
chore: Adjust auth doc url of apps to new links
2022-11-04 00:38:04 +01:00
Faruk AYDIN
f1d319601e chore: Adjust auth doc url of apps to new links 2022-11-04 00:19:41 +01:00
Ömer Faruk Aydın
00a0efc945 Merge branch 'main' into salesforce/auth 2022-11-03 23:34:20 +01:00
Ömer Faruk Aydın
f175ada782 Merge pull request #674 from automatisch/remove-reconnection-steps
Remove reconnection steps
2022-11-03 23:33:49 +01:00
Ali BARIN
ede55a70aa feat(salesforce): add authentication 2022-11-03 23:12:33 +01:00
Ali BARIN
7d82ca5d3c fix(InputCreator): show default value in dropdown 2022-11-03 23:12:33 +01:00
Ali BARIN
6a33636d9d refactor: compute reconnectionSteps out of auth steps 2022-11-03 23:12:09 +01:00
Ali BARIN
a03c60bd90 refactor: remove step property out of app auth 2022-11-03 23:12:09 +01:00
Ömer Faruk Aydın
4b122bd19e Merge pull request #675 from automatisch/remove-github-token-info-func
refactor(github): remove getTokenInfo function
2022-11-03 23:11:17 +01:00
Ali BARIN
c386b001ba refactor(github): remove getTokenInfo function 2022-11-03 20:16:57 +01:00
Ömer Faruk Aydın
9d5e54c2d9 Merge pull request #673 from automatisch/issue-495
feat(slack): add bot capability in send message action
2022-11-03 18:04:25 +01:00
Ali BARIN
2ffce75056 refactor(slack): move scopes out of function definition 2022-11-03 18:01:35 +01:00
Ali BARIN
ff76e37efc docs(slack): add bot token scope for bot functionality 2022-11-03 01:08:27 +01:00
Ali BARIN
ae3d547c3f fix(slack): uncomment users.profile:read user scope 2022-11-03 00:56:42 +01:00
Ali BARIN
a52e365b88 docs(slack): describe oauth2 connection 2022-11-02 21:01:55 +01:00
Ali BARIN
fa7b7d8781 feat(slack): add bot capability in send message action 2022-11-02 20:29:59 +01:00
Ali BARIN
f057501611 feat(slack): use oauth2 authentication flow 2022-11-02 20:28:07 +01:00
Ali BARIN
e740d4fe25 feat(slack): use current user to validate connection 2022-11-02 20:28:07 +01:00
Ali BARIN
dbcf8fde7f feat(types): add additionalProperties in requestConfig 2022-11-02 20:28:07 +01:00
Ali BARIN
b4a4f47f78 feat(slack): add get-current-user helper 2022-11-02 20:28:07 +01:00
Ali BARIN
362c8dc25d fix: keep parameters not needing computation 2022-11-02 20:28:07 +01:00
Ömer Faruk Aydın
18b0a41a40 Merge pull request #672 from automatisch/fix/docs-app-images
fix: Use withBase method for app images on documentation
2022-11-02 19:08:09 +01:00
Faruk AYDIN
d43f77a894 fix: Use withBase method for app images on documentation 2022-11-02 18:48:34 +01:00
Ömer Faruk Aydın
aeeb058ebb Merge pull request #670 from automatisch/docs/restructure-apps
Restructure apps on docs
2022-11-01 22:55:38 +01:00
Faruk AYDIN
e57122c4ff refactor: Restructure names, keys and descs of triggers and actions 2022-11-01 22:48:22 +01:00
Faruk AYDIN
802a9bd2e2 docs: Restructure apps documentation, add triggers and actions 2022-11-01 22:40:12 +01:00
Ömer Faruk Aydın
b63603afb6 Merge pull request #668 from automatisch/refactor/action-arguments
refactor: Use only arguments for action definitions
2022-11-01 00:50:45 +01:00
Faruk AYDIN
d6c0690324 refactor: Differentiate trigger and action for get app helper 2022-10-31 23:31:28 +01:00
Faruk AYDIN
1cfe84fc3d refactor: Use only arguments for action definitions 2022-10-31 23:25:55 +01:00
Ömer Faruk Aydın
bcfc9beb99 Merge pull request #669 from automatisch/docs/remove-typeform-connection
docs: Remove typeform connection guide
2022-10-31 22:55:19 +01:00
Faruk AYDIN
cb839637ab docs: Remove typeform connection guide 2022-10-31 22:09:03 +01:00
Ömer Faruk Aydın
6dc795fe45 Merge pull request #667 from automatisch/refactor/user-args-for-actions
refactor: Specify only the arguments for trigger definitions
2022-10-31 22:04:31 +01:00
Faruk AYDIN
b3f216209a refactor: Specify only the arguments for trigger definitions 2022-10-31 18:12:12 +01:00
Ömer Faruk Aydın
571bf6bfa7 Merge pull request #666 from automatisch/discord/send-message-to-channel
feat(discord): add send message to channel action
2022-10-30 23:16:26 +01:00
Ali BARIN
f272e5c4a8 docs: add discord in connections 2022-10-30 23:12:04 +01:00
Ali BARIN
d8a91ac62d docs(discord): describe discord setup 2022-10-30 22:52:56 +01:00
Ömer Faruk Aydın
92b0c5e409 Merge pull request #665 from automatisch/chore/remove-sorting
chore: Remove sorting logic from flow service
2022-10-30 22:29:08 +01:00
Ali BARIN
dfa076ba89 feat(discord): add send message to channel action 2022-10-30 22:28:50 +01:00
Ali BARIN
79e609e682 feat(discord): add list channels data 2022-10-30 22:28:16 +01:00
Ali BARIN
9c40bc5863 fix(web): discard failed auth process 2022-10-30 22:27:27 +01:00
Faruk AYDIN
7b18937530 chore: Remove sorting logic from flow service 2022-10-30 22:25:32 +01:00
Ali BARIN
c2b020fc94 feat(discord): use bot auth instead of user 2022-10-30 22:25:11 +01:00
Ömer Faruk Aydın
798769d5ae Merge pull request #664 from automatisch/chore/update-bullmq
chore: Update BullMQ version to 3.0.0
2022-10-30 21:14:19 +01:00
Faruk AYDIN
9546d76dc9 fix: Use pattern instead of cron for repeatable job options 2022-10-30 18:57:35 +01:00
Faruk AYDIN
6964f7ebec chore: Update BullMQ version to 3.0.0 2022-10-30 18:52:53 +01:00
Ömer Faruk Aydın
e58b7166ad Merge pull request #662 from automatisch/chore/remove-redundant-libraries
chore: Remove redundant libraries of backend
2022-10-30 16:58:26 +01:00
Faruk AYDIN
d881cfeeb0 chore: Add type file to twilio app 2022-10-30 16:55:49 +01:00
Faruk AYDIN
854dc6f61a chore: Remove redundant libraries of backend 2022-10-30 16:50:33 +01:00
Ali BARIN
1a3345e521 chore(backend): remove excluding apps in tsconfig 2022-10-30 15:49:19 +01:00
Ömer Faruk Aydın
8632b29719 Merge pull request #659 from automatisch/feat/http-basic-auth-for-bullmq
feat: Add http basic auth for BullMQ dashboard
2022-10-30 15:20:02 +01:00
Ömer Faruk Aydın
24a3c3fd1c Merge pull request #660 from automatisch/chore/use-string-for-app-keys
chore: Remove available apps enum type and use string instead
2022-10-30 15:19:51 +01:00
Faruk AYDIN
115a46d2e7 chore: Remove available apps enum type and use string instead 2022-10-30 15:17:10 +01:00
Faruk AYDIN
cd5502fdde feat: Add http basic auth for BullMQ dashboard 2022-10-30 15:11:22 +01:00
Ömer Faruk Aydın
53bbde4314 Merge pull request #652 from automatisch/feature/new-sms-trigger
feat: Implement twilio new sms trigger
2022-10-30 14:39:23 +01:00
Faruk AYDIN
9506c22016 feat: Implement twilio receive sms trigger 2022-10-30 14:06:35 +01:00
Ömer Faruk Aydın
f3f9af9fdf Merge pull request #658 from automatisch/discord/auth
feat(discord): add auth
2022-10-30 13:37:14 +01:00
Ali BARIN
2f9e40fc0e feat(discord): add auth 2022-10-30 12:47:35 +01:00
Ömer Faruk Aydın
c46fbef03c Merge pull request #657 from automatisch/embed-actions-triggers-data-auth-in-app
refactor: inline auth, data, triggers and actions in apps
2022-10-28 23:33:22 +02:00
Ali BARIN
9a973c8257 refactor: inline auth, data, triggers and actions in apps 2022-10-28 22:38:52 +02:00
Ömer Faruk Aydın
c2c8973dd5 Merge pull request #656 from automatisch/feature/docs-search
feat: Add search functionality to docs
2022-10-28 20:51:42 +02:00
Faruk AYDIN
b178807a39 feat: Add search functionality to docs 2022-10-28 20:45:22 +02:00
Ömer Faruk Aydın
179fe512af Merge pull request #655 from automatisch/refactor/integration-structure
Refactor integration structure
2022-10-28 20:14:18 +02:00
Ömer Faruk Aydın
964a38db2c chore: Add explanations to early exit conditions of global variable
Co-authored-by: Ali BARIN <ali.barin53@gmail.com>
2022-10-28 20:07:31 +02:00
Faruk AYDIN
2b38e5a0dd chore: Use last 2000 records as deduplication list 2022-10-28 13:46:08 +02:00
Faruk AYDIN
d8e1f6df81 refactor: Remove test run and completion check for flick triggers 2022-10-28 13:43:01 +02:00
Faruk AYDIN
c3a54e0c69 feat: Introduce http error class 2022-10-28 13:36:28 +02:00
Faruk AYDIN
2b6ff7de41 refactor: Remove dedupeStrategy field from triggers 2022-10-28 13:23:51 +02:00
Faruk AYDIN
ae58e629ea fix: Do not push recent trigger item in case of already processed 2022-10-28 13:22:15 +02:00
Faruk AYDIN
18ccb25b29 refactor: Remove is already processed from new feed items 2022-10-28 01:16:32 +02:00
Faruk AYDIN
74d7e472af refactor: Remove test run and completion check from new photos 2022-10-28 01:12:20 +02:00
Faruk AYDIN
232ba230eb refactor: Remove test run and completion check from new pull requests 2022-10-28 01:12:20 +02:00
Faruk AYDIN
031e175e9f refactor: Remove test run and completion check from new issues 2022-10-28 01:12:20 +02:00
Faruk AYDIN
87fdc02224 refactor: Remove test run and completion logic from new watchers 2022-10-28 01:12:20 +02:00
Faruk AYDIN
3308a44c2b refactor: Remove testRun and completion check from new stargazers 2022-10-28 01:12:20 +02:00
Faruk AYDIN
796121acfc chore: Remove redundant IActionOutput type import 2022-10-28 01:12:20 +02:00
Faruk AYDIN
8e0553147b refactor: Remove testRun logic from get user followers 2022-10-28 01:12:20 +02:00
Faruk AYDIN
96007a1f48 refactor: Remove pagination and test run checks from user tweets 2022-10-28 01:12:20 +02:00
Faruk AYDIN
5189e5b131 refactor: Adjust pagination and test run for search tweets 2022-10-28 01:12:20 +02:00
Faruk AYDIN
11dc650e0e refactor: Remove dedupe strategies, use only dedupe list 2022-10-28 01:12:20 +02:00
Faruk AYDIN
baebec2270 feat: Add base and early exit error classes 2022-10-28 01:12:20 +02:00
Ali BARIN
419d84da97 feat(rss): add new items in feed trigger 2022-10-28 00:57:48 +02:00
Faruk AYDIN
7d7c96274d feat: Implement send sms action of twilio 2022-10-26 18:35:41 +02:00
Ömer Faruk Aydın
4553084503 Merge pull request #649 from automatisch/flickr/new-album
feat(flickr): add new albums trigger
2022-10-26 18:21:10 +02:00
Ömer Faruk Aydın
30bd56b984 Merge pull request #650 from automatisch/fix/e2e-tests-more-than-one-app
test(list-apps): assert apps length to be at least 1
2022-10-26 18:20:21 +02:00
Ali BARIN
4292517a86 test(list-apps): assert apps length to be at least 1 2022-10-26 01:26:44 +02:00
Ali BARIN
f8c7e601a5 feat(flickr): add new albums trigger 2022-10-26 00:54:29 +02:00
Ali BARIN
fbc867b4fa fix: cover _ in variable regex 2022-10-26 00:11:59 +02:00
Ali BARIN
1644bfeb43 feat(flickr): add new photos in an album trigger 2022-10-26 00:11:49 +02:00
Ali BARIN
7cc82eb143 feat(flickr): add list albums dynamic data 2022-10-26 00:11:49 +02:00
Ömer Faruk Aydın
5c13c81de9 Merge pull request #646 from automatisch/docs/twilio-connection
docs: Add connection setup guide for Twilio
2022-10-25 22:38:27 +02:00
Faruk AYDIN
1b80db1eba docs: Add connection setup guide for Twilio 2022-10-25 22:02:47 +02:00
Faruk AYDIN
30d2740368 feat: Implement twilio connection 2022-10-25 20:08:11 +02:00
Ömer Faruk Aydın
8fb58cf662 Merge pull request #644 from automatisch/docs/available-apps
Docs/available apps
2022-10-25 20:01:06 +02:00
Ömer Faruk Aydın
3b4ad0d9c7 Merge pull request #643 from automatisch/feature/smtp-send-email
feat: Implement send email action
2022-10-25 19:59:22 +02:00
Ömer Faruk Aydın
abe4ebe66a Merge pull request #642 from automatisch/refactor/use-set-action-item
refactor: Use setActionItem method to push action data item
2022-10-25 19:59:13 +02:00
Ömer Faruk Aydın
ad1514c60b Merge pull request #641 from automatisch/fix/stargazer-data-order
fix: Iterate stargazer data reverse-chronologically
2022-10-25 19:59:03 +02:00
Faruk AYDIN
81ff07ec28 docs: Add generic explanation to github connection guide 2022-10-25 19:57:36 +02:00
Faruk AYDIN
7cd45a5ca3 docs: Sort connections alphabetically on the sidebar 2022-10-25 19:57:36 +02:00
Faruk AYDIN
e27a941425 docs: Use limited word for explaining available apps 2022-10-25 19:57:36 +02:00
Faruk AYDIN
f2c9adf2ef docs: Add recently implemented apps to available apps 2022-10-25 19:57:36 +02:00
Faruk AYDIN
8dfb919aa0 feat: Implement send email action 2022-10-25 19:50:36 +02:00
Faruk AYDIN
4cfae2d9c6 refactor: Use setActionItem method to push action data item 2022-10-25 19:49:33 +02:00
Ali BARIN
6b8e44a952 fix(flickr): name new photo trigger as plural 2022-10-25 19:49:02 +02:00
Ali BARIN
6b40d206f8 feat(flickr): add new fav photos trigger 2022-10-25 19:49:02 +02:00
Ali BARIN
524c3c22dc refactor(flickr): add isAlreadyProcessed for greatest dedupe strategy 2022-10-25 19:49:02 +02:00
Faruk AYDIN
d39cf2f051 fix: Iterate stargazer data reverse-chronologically 2022-10-25 19:48:37 +02:00
Faruk AYDIN
b2caea9b9b chore: Remove temporary app list from app model 2022-10-24 18:36:07 +02:00
Faruk AYDIN
ea76b5b762 refactor: Use pushTriggerItem utility function for triggers 2022-10-24 18:24:08 +02:00
Ömer Faruk Aydın
bda7ad9f2f Merge pull request #637 from automatisch/flickr/new-photos
feat(flickr): add new photos trigger
2022-10-23 23:43:23 +02:00
Ali BARIN
aa8d1d984c fix(github): iterate over next pages in actual run 2022-10-23 21:29:00 +02:00
Ali BARIN
4d54a608a6 feat(flickr): add new photos trigger 2022-10-23 21:28:53 +02:00
Ömer Faruk Aydın
6466606f49 Merge pull request #636 from automatisch/docs/smtp-guide
Add connection guide for SMTP
2022-10-23 20:29:17 +02:00
Faruk AYDIN
ca52bee808 docs: Remove redundant sentence from flick connection guide 2022-10-23 20:25:13 +02:00
Faruk AYDIN
8e3ef4873f docs: Add connection guide for SMTP 2022-10-23 20:24:29 +02:00
Faruk AYDIN
274d0eb2fd chore: Use argument option also for action substep argument 2022-10-23 20:12:48 +02:00
Faruk AYDIN
61cf45697c fix: Use arguments and formattedData for smtp auth 2022-10-23 20:12:48 +02:00
Faruk AYDIN
cb8f6f7b75 feat: Add argument option to connections of get app query 2022-10-23 20:12:48 +02:00
Faruk AYDIN
3978e8cd0d feat: Implement smtp connection 2022-10-23 20:12:48 +02:00
Faruk AYDIN
d816446c5e docs: Explain how to disable telemetry 2022-10-23 17:55:41 +02:00
Ömer Faruk Aydın
70f1aaab15 Merge pull request #631 from automatisch/add-flickr-auth
feat(flickr): add authentication
2022-10-23 00:12:51 +02:00
Ali BARIN
cccdb49075 docs(flickr): describe authentication 2022-10-23 00:02:59 +02:00
Ali BARIN
71d6a57cdc feat(flickr): add authentication 2022-10-22 23:55:06 +02:00
Ömer Faruk Aydın
05075fc1c4 Merge pull request #632 from automatisch/feature/extended-error-handler
Extend error handling logic to capture errors
2022-10-22 23:11:02 +02:00
Faruk AYDIN
a1692c3c37 fix: Adjust sorting for new stargazers trigger 2022-10-22 21:47:39 +02:00
Faruk AYDIN
d5cecde0e1 fix: Adjust sorting for new pull requests 2022-10-22 21:47:21 +02:00
Faruk AYDIN
b0dc0adf0e chore: Remove postgresql app with the old architecture 2022-10-22 21:09:38 +02:00
Faruk AYDIN
2bb058da36 fix: Adjust action item of slack post message 2022-10-22 19:50:25 +02:00
Faruk AYDIN
a56135ca57 refactor: Restructure apps with new data pushing logic 2022-10-22 19:29:02 +02:00
Faruk AYDIN
bcff9f5a9e refactor: Remove dedundant httpError assignment 2022-10-22 13:49:35 +02:00
Faruk AYDIN
59b9c66a91 refactor: Use global output with github stargazers 2022-10-22 13:49:32 +02:00
Faruk AYDIN
0f63f6d453 refactor: Use global output with scheduler triggers 2022-10-22 13:48:56 +02:00
Faruk AYDIN
585adb14e6 refactor: Use global output with slack actions 2022-10-22 13:48:56 +02:00
Faruk AYDIN
00be50d2fd refactor: Restructure create tweet with new global variable 2022-10-22 13:48:56 +02:00
Faruk AYDIN
7e2dd95134 chore: Differentiate trigger and action output types 2022-10-22 13:48:55 +02:00
Faruk AYDIN
64807fbc11 refactor: Add sort logic into my tweets trigger 2022-10-22 13:48:55 +02:00
Faruk AYDIN
204a8ffb7f refactor: No need to return from run methods for twitter triggers 2022-10-22 13:48:55 +02:00
Faruk AYDIN
aef097becb refactor: Use new error handling structure for user tweets 2022-10-22 13:48:55 +02:00
Faruk AYDIN
9a743fb4a8 feat: Capture unhandled errors by restructuring apps 2022-10-22 13:48:49 +02:00
Ali BARIN
525472d3e0 feat(github): add create issue action 2022-10-21 21:25:05 +02:00
Ali BARIN
d2e4f0d143 feat(github): add new watcher trigger 2022-10-21 21:24:55 +02:00
Ali BARIN
120a4c2b07 fix: mark step completed only when successful 2022-10-21 21:24:47 +02:00
Ali BARIN
97f88d6c4a feat(github): add new pull requests trigger 2022-10-21 21:24:47 +02:00
Ali BARIN
1613ab503c refactor(github): replace generateRequest with $.http 2022-10-20 20:45:52 +02:00
Faruk AYDIN
45bf274dc0 fix: Trigger sorting for tweets 2022-10-20 00:38:25 +02:00
Ömer Faruk Aydın
858737c25c Merge pull request #625 from automatisch/github-new-stargazers
feat(github): add new stargazer trigger
2022-10-20 00:28:48 +02:00
Ömer Faruk Aydın
ee31cfd390 Merge branch 'main' into github-new-stargazers 2022-10-20 00:05:01 +02:00
Ömer Faruk Aydın
5dae0edcf3 Merge pull request #624 from automatisch/github-new-issues
feat(github): add new issues trigger
2022-10-20 00:04:49 +02:00
Ali BARIN
e4dd7e454a feat(github): add new stargazer trigger 2022-10-19 22:57:10 +02:00
Ali BARIN
486e817a40 fix(github): return empty without repo in getRepoOwnerAndRepo helper 2022-10-19 22:56:52 +02:00
Ali BARIN
91ec19c7df fix(useDynamicData): throw error when dependency is undefined 2022-10-19 22:56:52 +02:00
Ali BARIN
f3f4ea5b60 fix(action): create empty execution step if needed 2022-10-19 22:56:52 +02:00
Ali BARIN
859b3e2db8 fix(trigger): create empty execution step if needed 2022-10-19 22:56:52 +02:00
Ali BARIN
3d836b45bf fix(github): return empty in list labels without repo 2022-10-19 22:56:52 +02:00
Ali BARIN
4670e2fe0c fix: reset field when its deps are not satisfied 2022-10-19 22:56:52 +02:00
Ali BARIN
3440b89b04 fix: use runtime parameters in get-data query 2022-10-19 22:56:52 +02:00
Ali BARIN
1458003536 feat(github): add new-issues trigger 2022-10-19 22:56:47 +02:00
Ali BARIN
eaf3c1ecfd feat(github): add list-labels dynamic data 2022-10-19 20:37:19 +02:00
Ali BARIN
12e73d59d5 feat(github): add list-repos dynamic data 2022-10-19 20:37:19 +02:00
Ali BARIN
51059b0f39 refactor(github): export default in get-repo-owner-and-repo 2022-10-19 20:37:19 +02:00
Ali BARIN
0b8b5aeebd feat(github): add paginate-all utility 2022-10-19 20:37:19 +02:00
Ali BARIN
cd795a55d3 feat: add parse-header-link helper 2022-10-19 20:37:19 +02:00
Ömer Faruk Aydın
922f86f20c Merge pull request #623 from automatisch/chore/remove-default-column-values
chore: Remove default values from fields of database models
2022-10-19 19:58:33 +02:00
Ömer Faruk Aydın
b8b2e3e3d2 Merge pull request #622 from automatisch/feature/add-status-to-get-executions
feat: Add dynamic status field to get executions graphQL query
2022-10-19 19:58:21 +02:00
Faruk AYDIN
74aed833bf chore: Remove default values from fields of database models 2022-10-19 19:22:11 +02:00
Faruk AYDIN
04a5378e1b feat: Add dynamic status field to get executions graphQL query 2022-10-19 18:48:02 +02:00
Ömer Faruk Aydın
dc0f374110 Merge pull request #620 from automatisch/refactor/restructure-slack
refactor: Use beforeRequest hook with slack app
2022-10-18 23:11:13 +02:00
Faruk AYDIN
7422f63c62 refactor: Use beforeRequest hook with slack app 2022-10-18 23:01:56 +02:00
Faruk AYDIN
3004cf1115 feat: Implement create tweet action 2022-10-18 19:57:53 +02:00
Faruk AYDIN
a5e114ac68 refactor: Restructure twitter app with beforeRequest hook 2022-10-18 19:45:00 +02:00
Faruk AYDIN
bc402883b3 feat: Add sitemap for the docs website 2022-10-18 18:50:26 +02:00
Ömer Faruk Aydın
72599c6683 Merge pull request #616 from automatisch/add-define-app-and-before-request
feat: add defineApp and beforeRequest
2022-10-18 00:34:56 +02:00
Ali BARIN
1760c6e454 feat: add defineApp and beforeRequest 2022-10-17 23:52:48 +02:00
Faruk AYDIN
3e05d7b5e9 fix: Use apiBaseUrl for twitter generate request method 2022-10-17 23:29:28 +02:00
Ömer Faruk Aydın
e9f7dcf030 Merge pull request #614 from automatisch/github-auth
refactor: clean up github and rewrite its auth
2022-10-17 00:20:32 +02:00
Ali BARIN
314787f39c refactor: clean up github and rewrite its auth 2022-10-16 23:34:21 +02:00
Ömer Faruk Aydın
a69cd51dda Merge pull request #613 from automatisch/feat/show-soft-deleted-executions
feat: Query soft-deleted executions and execution steps
2022-10-16 15:20:22 +02:00
Faruk AYDIN
d1932a107b feat: Query soft-deleted executions and execution steps 2022-10-16 15:00:08 +02:00
Ömer Faruk Aydın
670ebe5d37 Merge pull request #554 from automatisch/issue-553
feat: soft delete database entities
2022-10-16 13:43:15 +02:00
Ömer Faruk Aydın
b92e0b9660 Merge branch 'main' into issue-553 2022-10-16 13:23:07 +02:00
Ömer Faruk Aydın
664cd1d0cd Merge pull request #612 from ThuH2305/patch-4
docs(readme): remove trailing slash in docs link
2022-10-16 13:22:01 +02:00
Thu Huynh
efc4588a00 docs(readme): remove trailing slash in docs link 2022-10-16 13:20:15 +02:00
Ali BARIN
92a2d68a81 feat: add defineTrigger and defineAction 2022-10-16 11:15:05 +02:00
Ömer Faruk Aydın
ee9d095454 Merge pull request #610 from automatisch/chore/add-canonical-urls-to-docs
chore: Add canonical URLs to doc pages
2022-10-16 11:04:18 +02:00
Ömer Faruk Aydın
b9450e3c55 Merge pull request #609 from automatisch/fix/use-dev-dependecy-for-vitepress
fix: Use dev dependencies for vitepress
2022-10-16 11:04:06 +02:00
Faruk AYDIN
fe40e16e33 chore: Add canonical URLs to doc pages 2022-10-16 01:23:34 +02:00
Faruk AYDIN
8f5901753e fix: Use dev dependencies for vitepress 2022-10-16 00:50:21 +02:00
Thu Huynh
9005256cd3 chore(docs): update copyright date 2022-10-15 23:55:42 +02:00
Ali BARIN
ace2436e43 feat(FlowEditor): allow adding new connection in step 2022-10-15 23:52:37 +02:00
Ömer Faruk Aydın
d83db08b81 Merge pull request #606 from automatisch/feat/use-clean-doc-urls
feat: Use clean URLs for docs
2022-10-15 22:05:51 +02:00
Faruk AYDIN
fd0d4dceca feat: Use clean URLs for docs 2022-10-15 21:58:24 +02:00
Ali BARIN
6f0a763589 fix(scheduler): return TriggerDataItem 2022-10-15 20:43:11 +02:00
Ömer Faruk Aydın
0849801ae9 Merge pull request #603 from automatisch/issue-598
fix: only show apps with actions in action step
2022-10-15 13:41:09 +02:00
Ali BARIN
c0dfc101b2 fix: only show apps with actions in action step 2022-10-15 13:35:14 +02:00
Faruk AYDIN
b5e5ae7321 fix: Do not sort user tweets since it's in reverse-chronological order 2022-10-15 11:51:28 +02:00
Thu Huynh
39792d25d8 docs(readme): remove github community link 2022-10-14 23:57:15 +02:00
Ömer Faruk Aydın
20c0451c86 Merge pull request #600 from automatisch/refactor/remove-search-term
refactor: Do not expose search term separately for search tweets
2022-10-14 23:53:52 +02:00
Faruk AYDIN
8a883765d4 refactor: Do not expose search term separately for search tweets 2022-10-14 23:51:00 +02:00
Faruk AYDIN
cad42eed54 refactor: Remove test run methods from triggers 2022-10-14 23:47:50 +02:00
Ömer Faruk Aydın
a7f68f80d1 Merge branch 'main' into issue-553 2022-10-14 22:36:45 +02:00
Ömer Faruk Aydın
c422ca530d Merge pull request #596 from automatisch/refactor/background-jobs
refactor: Extract processor job into separate background jobs
2022-10-14 22:36:06 +02:00
Faruk AYDIN
237ab48d33 refactor: Restructure workers to work with services 2022-10-14 22:33:26 +02:00
Faruk AYDIN
628f872180 refactor: Implement test run helper to work with services 2022-10-14 20:18:58 +02:00
Faruk AYDIN
56a9aeece7 refactor: Extract processor job into separate background jobs 2022-10-13 18:45:30 +02:00
Faruk AYDIN
3c3bb82e97 refactor: Remove early exit strategy for twitter triggers 2022-10-12 23:23:08 +02:00
Faruk AYDIN
d9192f6e6b refactor: Introduce IActionOutput and ITriggerOutput types 2022-10-12 22:50:42 +02:00
Ali BARIN
2e31e821b3 Merge branch 'main' into issue-553 2022-10-12 18:02:53 +02:00
Zeynep Nur Temel
6895378d33 docs: fix description of Slack connection 2022-10-12 17:52:42 +02:00
Thu Huynh
f2f99fda31 docs: fix documentation link in README 2022-10-12 17:50:15 +02:00
Ali BARIN
56b7763488 chore: remove unused twilio app 2022-10-09 16:55:08 +02:00
Ali BARIN
12bbcd7887 chore: remove unused twitch app 2022-10-09 16:54:58 +02:00
Ali BARIN
b42c27b02f chore: remove unused typeform app 2022-10-09 16:54:52 +02:00
Ali BARIN
e34ac3c485 chore: remove unused smtp app 2022-10-09 16:54:46 +02:00
Ali BARIN
54bbc0d79c chore: remove unused gitlab app 2022-10-09 16:54:39 +02:00
Ali BARIN
3a186ebb1a chore: remove unused firebase app 2022-10-09 14:57:56 +02:00
Ali BARIN
119650048e chore: delete unused discord app 2022-10-09 14:57:23 +02:00
Ali BARIN
ec8e57972a Release v0.1.5 2022-10-09 10:21:05 +02:00
Ali BARIN
0d04e1534d chore: update cli version with 0.1.5 in Dockerfiles 2022-10-09 10:21:05 +02:00
Ali BARIN
a2602cd2c8 Merge branch 'main' into issue-553 2022-10-08 15:03:51 +02:00
Ali BARIN
56c8666f02 fix: associate executions in User model for types 2022-09-29 19:54:41 +02:00
Ali BARIN
1404650566 chore: update querybuilder type in flow model 2022-09-29 19:54:08 +02:00
Ali BARIN
6a438a34f0 chore: update query param type in pagination helper 2022-09-29 19:53:45 +02:00
Ali BARIN
816d63676d feat: add withSoftDeleted functionality in base model 2022-09-29 11:18:03 +02:00
Ali BARIN
8be352765e feat: soft delete database entities 2022-09-29 10:18:52 +02:00
Ali BARIN
038b38879f chore(backend): add soft delete migration 2022-09-28 21:38:33 +02:00
503 changed files with 9971 additions and 10286 deletions

View File

@@ -1,3 +1,4 @@
{
"editor.formatOnSave": true
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
}

View File

@@ -37,7 +37,6 @@ You can use `user@automatisch.io` email address and `sample` password to login t
## Community Links
- [Github](https://github.com/automatisch/automatisch)
- [Discord](https://discord.gg/dJSah9CVrC)
- [Twitter](https://twitter.com/automatischio)
@@ -49,4 +48,4 @@ If you have any questions or problems, please visit our GitHub discussions page,
## License
Automatisch is an open-source software with an [AGPL 3.0 license](https://github.com/automatisch/automatisch/blob/main/LICENSE.md).
Automatisch is an open-source software with the [AGPL 3.0 license](https://github.com/automatisch/automatisch/blob/main/LICENSE.md).

View File

@@ -1,11 +1,11 @@
version: "3.9"
version: '3.9'
services:
main:
build:
context: ../images/wait-for-postgres
context: ../images/main
network: host
ports:
- "3000:3000"
- '3000:3000'
depends_on:
- postgres
- redis
@@ -22,7 +22,7 @@ services:
- automatisch_storage:/automatisch/storage
worker:
build:
context: ../images/plain
context: ../images/worker
network: host
depends_on:
- main
@@ -32,16 +32,15 @@ services:
- POSTGRES_HOST=postgres
- POSTGRES_DATABASE=automatisch
- POSTGRES_USERNAME=automatisch_user
command: automatisch start-worker --env-file /automatisch/storage/.env
volumes:
- automatisch_storage:/automatisch/storage
postgres:
image: "postgres:14.5"
image: 'postgres:14.5'
environment:
POSTGRES_HOST_AUTH_METHOD: trust
POSTGRES_DB: automatisch
POSTGRES_USER: automatisch_user
redis:
image: "redis:7.0.4"
image: 'redis:7.0.4'
volumes:
automatisch_storage:

View File

@@ -3,7 +3,7 @@
set -e
until psql -h "$POSTGRES_HOST" -U "$POSTGRES_USERNAME" -d "$POSTGRES_HOST" -c '\q'; do
>&2 echo "Postgres is unavailable - sleeping"
>&2 echo "Waiting for Postgres to be ready..."
sleep 1
done

View File

@@ -1,5 +0,0 @@
# syntax=docker/dockerfile:1
FROM node:16
WORKDIR /automatisch
RUN yarn global add @automatisch/cli@0.1.5

View File

@@ -0,0 +1,10 @@
# syntax=docker/dockerfile:1
FROM node:16
WORKDIR /automatisch
RUN apt-get update && apt-get install -y postgresql-client
COPY ./wait-for-postgres.sh /automatisch/wait-for-postgres.sh
RUN yarn global add @automatisch/cli@0.1.5
CMD sh /automatisch/wait-for-postgres.sh automatisch start-worker --env-file /automatisch/storage/.env

View File

@@ -0,0 +1,11 @@
#!/bin/sh
set -e
until psql -h "$POSTGRES_HOST" -U "$POSTGRES_USERNAME" -d "$POSTGRES_HOST" -c '\q'; do
>&2 echo "Waiting for Postgres to be ready..."
sleep 1
done
>&2 echo "Postgres is up - executing command"
exec "$@"

View File

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

View File

@@ -1,7 +1,7 @@
HOST=localhost
PROTOCOL=http
PORT=3000
WEB_APP_URL=https://localhost:3001
WEB_APP_URL=http://localhost:3001
APP_ENV=development
POSTGRES_DATABASE=automatisch_development
POSTGRES_PORT=5432
@@ -14,3 +14,4 @@ APP_SECRET_KEY=sample-app-secret-key
REDIS_PORT=6379
REDIS_HOST=127.0.0.1
ENABLE_BULLMQ_DASHBOARD=false
SERVE_WEB_APP_SEPARATELY=true

View File

@@ -4,6 +4,6 @@ const client = new Client({
host: 'localhost',
user: 'postgres',
port: 5432,
})
});
export default client;

View File

@@ -4,7 +4,10 @@ import client from './client';
import User from '../../src/models/user';
import '../../src/config/orm';
export async function createUser(email = 'user@automatisch.io', password = 'sample') {
export async function createUser(
email = 'user@automatisch.io',
password = 'sample'
) {
const UNIQUE_VIOLATION_CODE = '23505';
const userParams = {
email,
@@ -29,14 +32,17 @@ export async function createUser(email = 'user@automatisch.io', password = 'samp
}
}
export const createDatabaseAndUser = async (database = appConfig.postgresDatabase, user = appConfig.postgresUsername) => {
export const createDatabaseAndUser = async (
database = appConfig.postgresDatabase,
user = appConfig.postgresUsername
) => {
await client.connect();
await createDatabase(database);
await createDatabaseUser(user);
await grantPrivileges(database, user);
await client.end();
}
};
export const createDatabase = async (database = appConfig.postgresDatabase) => {
const DUPLICATE_DB_CODE = '42P04';
@@ -51,7 +57,7 @@ export const createDatabase = async (database = appConfig.postgresDatabase) => {
logger.info(`Database: ${database} already exists!`);
}
}
};
export const createDatabaseUser = async (user = appConfig.postgresUsername) => {
const DUPLICATE_OBJECT_CODE = '42710';
@@ -68,25 +74,25 @@ export const createDatabaseUser = async (user = appConfig.postgresUsername) => {
logger.info(`Database User: ${user} already exists!`);
}
}
};
export const grantPrivileges = async (
database = appConfig.postgresDatabase, user = appConfig.postgresUsername
database = appConfig.postgresDatabase,
user = appConfig.postgresUsername
) => {
await client.query(
`GRANT ALL PRIVILEGES ON DATABASE ${database} TO ${user};`
);
logger.info(
`${user} has granted all privileges on ${database}!`
);
}
logger.info(`${user} has granted all privileges on ${database}!`);
};
export const dropDatabase = async () => {
if (appConfig.appEnv != 'development' && appConfig.appEnv != 'test') {
const errorMessage = 'Drop database command can be used only with development or test environments!'
const errorMessage =
'Drop database command can be used only with development or test environments!';
logger.error(errorMessage)
logger.error(errorMessage);
return;
}
@@ -94,13 +100,15 @@ export const dropDatabase = async () => {
await dropDatabaseAndUser();
await client.end();
}
};
export const dropDatabaseAndUser = async(database = appConfig.postgresDatabase, user = appConfig.postgresUsername) => {
export const dropDatabaseAndUser = async (
database = appConfig.postgresDatabase,
user = appConfig.postgresUsername
) => {
await client.query(`DROP DATABASE IF EXISTS ${database}`);
logger.info(`Database: ${database} removed!`);
await client.query(`DROP USER IF EXISTS ${user}`);
logger.info(`Database User: ${user} removed!`);
}
};

View File

@@ -10,7 +10,7 @@ const knexConfig = {
user: appConfig.postgresUsername,
password: appConfig.postgresPassword,
database: appConfig.postgresDatabase,
ssl: appConfig.postgresEnableSsl
ssl: appConfig.postgresEnableSsl,
},
pool: { min: 0, max: 20 },
migrations: {
@@ -20,7 +20,7 @@ const knexConfig = {
},
seeds: {
directory: __dirname + '/src/db/seeds',
}
}
},
};
export default knexConfig;

View File

@@ -1,10 +1,10 @@
{
"name": "@automatisch/backend",
"version": "0.1.5",
"version": "0.2.0",
"license": "AGPL-3.0",
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
"scripts": {
"dev": "ts-node-dev src/server.ts",
"dev": "ts-node-dev --exit-child src/server.ts",
"worker": "nodemon --watch 'src/**/*.ts' --exec 'ts-node' src/worker.ts",
"build": "tsc && yarn copy-statics",
"build:watch": "nodemon --watch 'src/**/*.ts' --watch 'bin/**/*.ts' --exec yarn build --ext ts",
@@ -22,28 +22,25 @@
"prebuild": "rm -rf ./dist"
},
"dependencies": {
"@automatisch/web": "^0.1.5",
"@automatisch/web": "^0.2.0",
"@bull-board/express": "^3.10.1",
"@gitbeaker/node": "^35.6.0",
"@graphql-tools/graphql-file-loader": "^7.3.4",
"@graphql-tools/load": "^7.5.2",
"@rudderstack/rudder-sdk-node": "^1.1.2",
"@slack/bolt": "3.10.0",
"@types/luxon": "^2.3.1",
"ajv-formats": "^2.1.1",
"axios": "0.24.0",
"bcrypt": "^5.0.1",
"bullmq": "^1.76.1",
"bullmq": "^3.0.0",
"copyfiles": "^2.4.1",
"cors": "^2.8.5",
"crypto-js": "^4.1.1",
"debug": "~2.6.9",
"discord.js": "13.2.0",
"dotenv": "^10.0.0",
"express": "~4.16.1",
"express-basic-auth": "^1.2.1",
"express-graphql": "^0.12.0",
"flickr-sdk": "3.10.0",
"googleapis": "89.0.0",
"fast-xml-parser": "^4.0.11",
"graphql-middleware": "^6.1.15",
"graphql-shield": "^7.5.0",
"graphql-tools": "^8.2.0",
@@ -57,11 +54,7 @@
"nodemailer": "6.7.0",
"oauth-1.0a": "^2.2.6",
"objection": "^3.0.0",
"octokit": "^1.7.1",
"pg": "^8.7.1",
"twilio": "3.70.0",
"twitch-js": "2.0.0-beta.42",
"twitter-api-v2": "1.6.0",
"winston": "^3.7.1"
},
"contributors": [
@@ -100,7 +93,7 @@
"url": "https://github.com/automatisch/automatisch/issues"
},
"devDependencies": {
"@automatisch/types": "^0.1.5",
"@automatisch/types": "^0.2.0",
"@types/bcrypt": "^5.0.0",
"@types/bull": "^3.15.8",
"@types/cors": "^2.8.12",

View File

@@ -1,4 +1,3 @@
import appConfig from './config/app';
import createError from 'http-errors';
import express, { Request, Response, NextFunction } from 'express';
import cors from 'cors';
@@ -15,15 +14,11 @@ import {
} from './helpers/create-bull-board-handler';
import injectBullBoardHandler from './helpers/inject-bull-board-handler';
if (appConfig.enableBullMQDashboard) {
createBullBoardHandler(serverAdapter);
}
const app = express();
if (appConfig.enableBullMQDashboard) {
injectBullBoardHandler(app, serverAdapter);
}
appAssetsHandler(app);

View File

@@ -0,0 +1,3 @@
import translateText from './translate-text';
export default [translateText];

View File

@@ -0,0 +1,77 @@
import qs from 'qs';
import defineAction from '../../../../helpers/define-action';
export default defineAction({
name: 'Translate Text',
key: 'translateText',
description: 'Translates text from one language to another.',
arguments: [
{
label: 'Text',
key: 'text',
type: 'string' as const,
required: true,
description: 'Text to be translated.',
variables: true,
},
{
label: 'Target Language',
key: 'targetLanguage',
type: 'dropdown' as const,
required: true,
description: 'Language to translate the text to.',
variables: false,
value: '',
options: [
{ label: 'Bulgarian', value: 'BG' },
{ label: 'Chinese (simplified)', value: 'ZH' },
{ label: 'Czech', value: 'CS' },
{ label: 'Danish', value: 'DA' },
{ label: 'Dutch', value: 'NL' },
{ label: 'English', value: 'EN' },
{ label: 'English (American)', value: 'EN-US' },
{ label: 'English (British)', value: 'EN-GB' },
{ label: 'Estonian', value: 'ET' },
{ label: 'Finnish', value: 'FI' },
{ label: 'French', value: 'FR' },
{ label: 'German', value: 'DE' },
{ label: 'Greek', value: 'EL' },
{ label: 'Hungarian', value: 'HU' },
{ label: 'Indonesian', value: 'ID' },
{ label: 'Italian', value: 'IT' },
{ label: 'Japanese', value: 'JA' },
{ label: 'Latvian', value: 'LV' },
{ label: 'Lithuanian', value: 'LT' },
{ label: 'Polish', value: 'PL' },
{ label: 'Portuguese', value: 'PT' },
{ label: 'Portuguese (Brazilian)', value: 'PT-BR' },
{
label:
'Portuguese (all Portuguese varieties excluding Brazilian Portuguese)',
value: 'PT-PT',
},
{ label: 'Romanian', value: 'RO' },
{ label: 'Russian', value: 'RU' },
{ label: 'Slovak', value: 'SK' },
{ label: 'Slovenian', value: 'SL' },
{ label: 'Spanish', value: 'ES' },
{ label: 'Swedish', value: 'SV' },
{ label: 'Turkish', value: 'TR' },
{ label: 'Ukrainian', value: 'UK' },
],
},
],
async run($) {
const stringifiedBody = qs.stringify({
text: $.step.parameters.text,
target_lang: $.step.parameters.targetLanguage,
});
const response = await $.http.post('/v2/translate', stringifiedBody);
$.setActionItem({
raw: response.data,
});
},
});

View File

@@ -0,0 +1,39 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg">
<metadata id="metadata4177">image/svg+xml</metadata>
<defs>
<clipPath id="clipPath4187" clipPathUnits="userSpaceOnUse">
<path id="path4189" d="m70.850157,393.069981l708.661,0l0,-425.197l-708.661,0l0,425.197z"/>
</clipPath>
</defs>
<g>
<title>background</title>
<rect fill="#ffffff" id="canvas_background" height="130" width="130" y="-1" x="-1"/>
</g>
<g>
<title>Layer 1</title>
<g fill="#FFF" transform="matrix(1.36265596065192,0,0,-1.3395561298863794,-403.63320244005325,311.0674198374575) " id="g4179">
<g fill="#FFF" id="g4183">
<g fill="#FFF" clip-path="url(#clipPath4187)" id="g4185">
<g fill="#FFF" id="g4191">
<path fill="#000000" fill-rule="nonzero" id="path4193" d="m411.83557,185.452083c0,-2.425 -0.463,-4.637 -1.3,-6.226c-1.009,-1.905 -2.173,-3.234 -3.366,-3.843c-1.641,-0.835 -3.707,-1.69 -6.586,-1.69l-4.044,0l0,22.753l3.221,0c4.28,0 7.213,-0.841 8.971,-2.572c2.09,-2.059 3.104,-4.814 3.104,-8.422m-1.89,15.206c-2.434,1.24 -6.053,1.24 -10.633,1.24l-7.069,0c-1.158,0 -2.097,-0.939 -2.097,-2.097l0,-29.463c0,-1.157 0.939,-2.096 2.097,-2.096l6.662,0c3.78,0 7.209,0.393 9.411,1.079c1.585,0.491 3.181,1.529 5.022,3.27c1.559,1.472 2.785,3.317 3.642,5.484c0.886,2.243 1.318,4.609 1.318,7.232c0,3.66 -0.771,6.809 -2.359,9.622c-1.441,2.558 -3.344,4.377 -5.994,5.729"/>
</g>
<g fill="#FFF" id="g4195">
<path fill="#000000" fill-rule="nonzero" id="path4197" d="m533.882353,173.693278l-10.436,0l0,27.36c0,0.467 -0.38,0.845 -0.845,0.845l-4.702,0c-0.466,0 -0.845,-0.378 -0.845,-0.845l0,-31.966c0,-0.467 0.379,-0.846 0.845,-0.846l15.983,0c0.465,0 0.848,0.379 0.848,0.846l0,3.759c0,0.469 -0.383,0.847 -0.848,0.847"/>
</g>
<g fill="#FFF" id="g4199">
<path fill="#000000" fill-rule="nonzero" id="path4201" d="m506.182158,181.465785c0,-2.225 -0.685,-4.08 -2.034,-5.516c-1.35,-1.437 -3.039,-2.167 -5.017,-2.167c-2.038,0 -3.751,0.718 -5.096,2.13c-1.34,1.415 -2.022,3.291 -2.022,5.576c0,2.242 0.682,4.081 2.024,5.471c1.343,1.391 3.056,2.096 5.094,2.096c2.026,0 3.724,-0.716 5.05,-2.132c1.326,-1.412 2.001,-3.249 2.001,-5.458m-5.813,13.324c-1.536,0 -3.091,-0.305 -4.385,-0.862c-1.508,-0.646 -2.894,-1.553 -3.903,-2.554l-0.601,-0.48l-2.708,2.543l-2.843,0c-0.465,0 -0.842,-0.377 -0.842,-0.842l0,-32.911c0,-0.467 0.378,-0.845 0.845,-0.845l4.701,0c0.467,0 0.847,0.379 0.847,0.847l0,12.166l0.598,-0.455c1.287,-1.204 2.95,-2.034 4.125,-2.52c1.249,-0.516 2.621,-0.78 4.076,-0.78c3.306,0 6.085,1.248 8.499,3.812c2.408,2.557 3.581,5.661 3.581,9.488c0,3.886 -1.176,7.13 -3.505,9.64c-2.302,2.491 -5.157,3.753 -8.485,3.753"/>
</g>
<g fill="#FFF" id="g4203">
<path fill="#000000" fill-rule="nonzero" id="path4205" d="m460.078642,184.036586l0.149,0.387c0.847,1.866 1.627,3.045 2.381,3.604c1.364,1.009 2.965,1.522 4.765,1.522c1.543,0 3.024,-0.499 4.396,-1.485c1.209,-0.865 2.09,-2.13 2.533,-3.616l0.101,-0.412l-14.325,0zm6.978,10.753c-3.801,0 -6.871,-1.255 -9.39,-3.836c-2.52,-2.584 -3.745,-5.741 -3.745,-9.651c0,-3.815 1.228,-6.902 3.757,-9.439c2.526,-2.535 5.709,-3.766 9.73,-3.766c2.584,0 4.824,0.454 6.656,1.348c1.543,0.752 2.915,1.843 4.079,3.243c0.167,0.203 0.233,0.472 0.172,0.728c-0.056,0.257 -0.23,0.472 -0.469,0.583l-3.782,1.739c-0.3,0.138 -0.656,0.089 -0.907,-0.128c-1.691,-1.459 -3.641,-2.2 -5.797,-2.2c-2.156,0 -3.793,0.489 -5.008,1.493c-1.29,1.072 -2.003,2.191 -2.332,3.679l19.696,0c0.466,0 0.842,0.375 0.844,0.841l0.007,0.835c0,4.359 -1.369,8.256 -3.757,10.695c-2.529,2.581 -5.718,3.836 -9.754,3.836"/>
</g>
<g fill="#FFF" id="g4207">
<path fill="#000000" fill-rule="nonzero" id="path4209" d="m428.942961,184.036586l0.148,0.386c0.854,1.871 1.635,3.05 2.387,3.605c1.36,1.009 2.963,1.522 4.763,1.522c1.542,0 3.022,-0.5 4.396,-1.485c1.208,-0.865 2.088,-2.13 2.533,-3.615l0.099,-0.413l-14.326,0zm6.979,10.753c-3.798,0 -6.871,-1.255 -9.39,-3.836c-2.521,-2.584 -3.745,-5.741 -3.745,-9.651c0,-3.816 1.227,-6.904 3.757,-9.439c2.526,-2.535 5.709,-3.766 9.73,-3.766c2.584,0 4.824,0.454 6.656,1.348c1.544,0.752 2.915,1.843 4.079,3.244c0.168,0.202 0.231,0.471 0.172,0.728c-0.056,0.256 -0.231,0.471 -0.469,0.582l-3.782,1.739c-0.298,0.138 -0.655,0.089 -0.907,-0.128c-1.688,-1.459 -3.639,-2.2 -5.797,-2.2c-2.156,0 -3.793,0.489 -5.005,1.494c-1.29,1.071 -2.006,2.189 -2.335,3.678l19.696,0c0.465,0 0.844,0.375 0.847,0.841l0.004,0.835c0,4.358 -1.369,8.254 -3.757,10.695c-2.526,2.581 -5.715,3.836 -9.754,3.836"/>
</g>
<g fill="#FFF" id="g4211">
<path fill="#000000" fill-rule="nonzero" id="path4213" d="m355.595244,180.065883c-2.797,0 -5.063,2.266 -5.063,5.063c0,0.295 0.025,0.584 0.075,0.865l-12.108,6.972c-0.881,-0.744 -2.02,-1.193 -3.263,-1.193c-2.797,0 -5.064,2.267 -5.064,5.063c0,2.796 2.267,5.063 5.064,5.063c2.797,0 5.064,-2.267 5.064,-5.063c0,-0.322 -0.033,-0.637 -0.091,-0.943l12.064,-6.946c0.889,0.775 2.051,1.245 3.322,1.245c2.795,0 5.062,-2.267 5.062,-5.063c0,-2.797 -2.267,-5.063 -5.062,-5.063m-15.357,-6.724c0,-2.796 -2.266,-5.063 -5.063,-5.063c-2.795,0 -5.062,2.267 -5.062,5.063c0,2.797 2.267,5.064 5.062,5.064c1.257,0 2.405,-0.459 3.29,-1.217l8.827,5.073c0.377,-1.089 0.963,-2.081 1.708,-2.929l-8.843,-5.083c0.052,-0.295 0.081,-0.598 0.081,-0.908m30.444,30.899l-24.042,13.738c-1.637,0.935 -3.647,0.935 -5.284,0l-24.04,-13.738c-1.66,-0.948 -2.684,-2.714 -2.684,-4.624l0,-27.946c0,-1.902 1.015,-3.661 2.663,-4.612l41.387,-23.889l0.007,16.968l12.01,6.921c1.65,0.951 2.666,2.71 2.666,4.614l0,27.944c0,1.91 -1.024,3.676 -2.683,4.624"/>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@@ -0,0 +1,33 @@
import verifyCredentials from './verify-credentials';
import isStillVerified from './is-still-verified';
export default {
fields: [
{
key: 'screenName',
label: 'Screen Name',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description:
'Screen name of your connection to be used on Automatisch UI.',
clickToCopy: false,
},
{
key: 'authenticationKey',
label: 'Authentication Key',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description: 'DeepL authentication key of your account.',
clickToCopy: false,
},
],
verifyCredentials,
isStillVerified,
};

View File

@@ -0,0 +1,9 @@
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,11 @@
import { IGlobalVariable } from '@automatisch/types';
const verifyCredentials = async ($: IGlobalVariable) => {
await $.http.get('/v2/usage');
await $.auth.set({
screenName: $.auth.data.screenName,
});
};
export default verifyCredentials;

View File

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

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: 'DeepL',
key: 'deepl',
iconUrl: '{BASE_URL}/apps/deepl/assets/favicon.svg',
authDocUrl: 'https://automatisch.io/docs/apps/deepl/connection',
supportsConnections: true,
baseUrl: 'https://deepl.com',
apiBaseUrl: 'https://api.deepl.com',
primaryColor: '0d2d45',
beforeRequest: [addAuthHeader],
auth,
actions,
});

View File

@@ -0,0 +1,3 @@
import sendMessageToChannel from './send-message-to-channel';
export default [sendMessageToChannel];

View File

@@ -0,0 +1,47 @@
import defineAction from '../../../../helpers/define-action';
export default defineAction({
name: 'Send a message to channel',
key: 'sendMessageToChannel',
description: 'Sends a message to a specific channel you specify.',
arguments: [
{
label: 'Channel',
key: 'channel',
type: 'dropdown' as const,
required: true,
description: 'Pick a channel to send the message to.',
variables: false,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listChannels',
},
],
},
},
{
label: 'Message text',
key: 'message',
type: 'string' as const,
required: true,
description: 'The content of your new message.',
variables: true,
},
],
async run($) {
const data = {
content: $.step.parameters.message as string,
};
const response = await $.http?.post(
`/channels/${$.step.parameters.channel}/messages`,
data
);
$.setActionItem({ raw: response.data });
},
});

View File

@@ -0,0 +1,22 @@
import { IField, IGlobalVariable } from '@automatisch/types';
import { URLSearchParams } from 'url';
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.consumerKey as string,
redirect_uri: callbackUrl,
response_type: 'code',
permissions: '2146958591',
scope: scopes.join(' '),
});
const url = `${$.app.apiBaseUrl}/oauth2/authorize?${searchParams.toString()}`;
await $.auth.set({ url });
}

View File

@@ -0,0 +1,61 @@
import generateAuthUrl from './generate-auth-url';
import verifyCredentials from './verify-credentials';
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/discord/connections/add',
placeholder: null,
description:
'When asked to input an OAuth callback or redirect URL in Discord OAuth, enter the URL above.',
docUrl: 'https://automatisch.io/docs/discord#oauth-redirect-url',
clickToCopy: true,
},
{
key: 'consumerKey',
label: 'Consumer Key',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
docUrl: 'https://automatisch.io/docs/discord#consumer-key',
clickToCopy: false,
},
{
key: 'consumerSecret',
label: 'Consumer Secret',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
docUrl: 'https://automatisch.io/docs/discord#consumer-secret',
clickToCopy: false,
},
{
key: 'botToken',
label: 'Bot token',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
docUrl: 'https://automatisch.io/docs/discord#bot-token',
clickToCopy: false,
},
],
generateAuthUrl,
verifyCredentials,
isStillVerified,
};

View File

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

View File

@@ -0,0 +1,53 @@
import { IGlobalVariable, IField } from '@automatisch/types';
import { URLSearchParams } from 'url';
import scopes from '../common/scopes';
import getCurrentUser from '../common/get-current-user';
const verifyCredentials = async ($: IGlobalVariable) => {
const oauthRedirectUrlField = $.app.auth.fields.find(
(field: IField) => field.key == 'oAuthRedirectUrl'
);
const callbackUrl = oauthRedirectUrlField.value as string;
const params = new URLSearchParams({
client_id: $.auth.data.consumerKey as string,
redirect_uri: callbackUrl,
response_type: 'code',
scope: scopes.join(' '),
client_secret: $.auth.data.consumerSecret as string,
code: $.auth.data.code as string,
grant_type: 'authorization_code',
});
const { data: verifiedCredentials } = await $.http.post(
'/oauth2/token',
params.toString()
);
const {
access_token: accessToken,
refresh_token: refreshToken,
expires_in: expiresIn,
scope: scope,
token_type: tokenType,
guild: { id: guildId, name: guildName },
} = verifiedCredentials;
await $.auth.set({
accessToken,
refreshToken,
expiresIn,
scope,
tokenType,
});
const user = await getCurrentUser($);
await $.auth.set({
userId: user.id,
screenName: user.username,
email: user.email,
guildId,
guildName,
});
};
export default verifyCredentials;

View File

@@ -1,99 +0,0 @@
import type {
IAuthentication,
IApp,
IField,
IJSONObject,
} from '@automatisch/types';
import { URLSearchParams } from 'url';
import axios, { AxiosInstance } from 'axios';
export default class Authentication implements IAuthentication {
appData: IApp;
connectionData: IJSONObject;
client: AxiosInstance = axios.create({
baseURL: 'https://discord.com/api/',
});
scope: string[] = ['identify', 'email'];
constructor(appData: IApp, connectionData: IJSONObject) {
this.appData = appData;
this.connectionData = connectionData;
}
get oauthRedirectUrl() {
return this.appData.fields.find(
(field: IField) => field.key == 'oAuthRedirectUrl'
).value;
}
async createAuthData() {
const searchParams = new URLSearchParams({
client_id: this.connectionData.consumerKey as string,
redirect_uri: this.oauthRedirectUrl,
response_type: 'code',
scope: this.scope.join(' '),
});
const url = `https://discord.com/api/oauth2/authorize?${searchParams.toString()}`;
return { url };
}
async verifyCredentials() {
const params = new URLSearchParams({
client_id: this.connectionData.consumerKey as string,
redirect_uri: this.oauthRedirectUrl,
response_type: 'code',
scope: this.scope.join(' '),
client_secret: this.connectionData.consumerSecret as string,
code: this.connectionData.oauthVerifier as string,
grant_type: 'authorization_code',
});
const { data: verifiedCredentials } = await this.client.post(
'/oauth2/token',
params.toString()
);
const {
access_token: accessToken,
refresh_token: refreshToken,
expires_in: expiresIn,
scope: scope,
token_type: tokenType,
} = verifiedCredentials;
const { data: user } = await this.client.get('/users/@me', {
headers: {
Authorization: `${tokenType} ${accessToken}`,
},
});
return {
consumerKey: this.connectionData.consumerKey,
consumerSecret: this.connectionData.consumerSecret,
accessToken,
refreshToken,
expiresIn,
scope,
tokenType,
userId: user.id,
screenName: user.username,
email: user.email,
};
}
async isStillVerified() {
try {
await this.client.get('/users/@me', {
headers: {
Authorization: `${this.connectionData.tokenType} ${this.connectionData.accessToken}`,
},
});
return true;
} catch {
return false;
}
}
}

View File

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

View File

@@ -0,0 +1,10 @@
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
const getCurrentUser = async ($: IGlobalVariable): Promise<IJSONObject> => {
const response = await $.http.get('/users/@me');
const currentUser = response.data;
return currentUser;
};
export default getCurrentUser;

View File

@@ -0,0 +1,3 @@
const scopes = ['bot', 'identify'];
export default scopes;

View File

@@ -0,0 +1,3 @@
import listChannels from './list-channels';
export default [listChannels];

View File

@@ -0,0 +1,34 @@
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
export default {
name: 'List channels',
key: 'listChannels',
async run($: IGlobalVariable) {
const channels: {
data: IJSONObject[];
error: IJSONObject | null;
} = {
data: [],
error: null,
};
const response = await $.http.get(
`/guilds/${$.auth.data.guildId}/channels`
);
channels.data = response.data
.filter((channel: IJSONObject) => {
// filter in text channels only
return channel.type === 0;
})
.map((channel: IJSONObject) => {
return {
value: channel.id,
name: channel.name,
};
});
return channels;
},
};

View File

@@ -1,15 +1,22 @@
import Authentication from './authentication';
import {
IService,
IAuthentication,
IApp,
IJSONObject,
} from '@automatisch/types';
import defineApp from '../../helpers/define-app';
import addAuthHeader from './common/add-auth-header';
import auth from './auth';
import dynamicData from './dynamic-data';
import actions from './actions';
import triggers from './triggers';
export default class Discord implements IService {
authenticationClient: IAuthentication;
constructor(appData: IApp, connectionData: IJSONObject) {
this.authenticationClient = new Authentication(appData, connectionData);
}
}
export default defineApp({
name: 'Discord',
key: 'discord',
iconUrl: '{BASE_URL}/apps/discord/assets/favicon.svg',
authDocUrl: 'https://automatisch.io/docs/apps/discord/connection',
supportsConnections: true,
baseUrl: 'https://discord.com',
apiBaseUrl: 'https://discord.com/api',
primaryColor: '5865f2',
beforeRequest: [addAuthHeader],
auth,
dynamicData,
triggers,
actions,
});

View File

@@ -1,219 +0,0 @@
{
"name": "Discord",
"key": "discord",
"iconUrl": "{BASE_URL}/apps/discord/assets/favicon.svg",
"docUrl": "https://automatisch.io/docs/discord",
"primaryColor": "5865f2",
"supportsConnections": true,
"fields": [
{
"key": "oAuthRedirectUrl",
"label": "OAuth Redirect URL",
"type": "string",
"required": true,
"readOnly": true,
"value": "{WEB_APP_URL}/app/discord/connections/add",
"placeholder": null,
"description": "When asked to input an OAuth callback or redirect URL in Discord OAuth, enter the URL above.",
"docUrl": "https://automatisch.io/docs/discord#oauth-redirect-url",
"clickToCopy": true
},
{
"key": "consumerKey",
"label": "Consumer Key",
"type": "string",
"required": true,
"readOnly": false,
"value": null,
"placeholder": null,
"description": null,
"docUrl": "https://automatisch.io/docs/discord#consumer-key",
"clickToCopy": false
},
{
"key": "consumerSecret",
"label": "Consumer Secret",
"type": "string",
"required": true,
"readOnly": false,
"value": null,
"placeholder": null,
"description": null,
"docUrl": "https://automatisch.io/docs/discord#consumer-secret",
"clickToCopy": false
}
],
"authenticationSteps": [
{
"step": 1,
"type": "mutation",
"name": "createConnection",
"arguments": [
{
"name": "key",
"value": "{key}"
},
{
"name": "formattedData",
"value": null,
"properties": [
{
"name": "consumerKey",
"value": "{fields.consumerKey}"
},
{
"name": "consumerSecret",
"value": "{fields.consumerSecret}"
}
]
}
]
},
{
"step": 2,
"type": "mutation",
"name": "createAuthData",
"arguments": [
{
"name": "id",
"value": "{createConnection.id}"
}
]
},
{
"step": 3,
"type": "openWithPopup",
"name": "openAuthPopup",
"arguments": [
{
"name": "url",
"value": "{createAuthData.url}"
}
]
},
{
"step": 4,
"type": "mutation",
"name": "updateConnection",
"arguments": [
{
"name": "id",
"value": "{createConnection.id}"
},
{
"name": "formattedData",
"value": null,
"properties": [
{
"name": "oauthVerifier",
"value": "{openAuthPopup.code}"
}
]
}
]
},
{
"step": 5,
"type": "mutation",
"name": "verifyConnection",
"arguments": [
{
"name": "id",
"value": "{createConnection.id}"
}
]
}
],
"reconnectionSteps": [
{
"step": 1,
"type": "mutation",
"name": "resetConnection",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
}
]
},
{
"step": 2,
"type": "mutation",
"name": "updateConnection",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
},
{
"name": "formattedData",
"value": null,
"properties": [
{
"name": "consumerKey",
"value": "{fields.consumerKey}"
},
{
"name": "consumerSecret",
"value": "{fields.consumerSecret}"
}
]
}
]
},
{
"step": 3,
"type": "mutation",
"name": "createAuthData",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
}
]
},
{
"step": 4,
"type": "openWithPopup",
"name": "openAuthPopup",
"arguments": [
{
"name": "url",
"value": "{createAuthData.url}"
}
]
},
{
"step": 5,
"type": "mutation",
"name": "updateConnection",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
},
{
"name": "formattedData",
"value": null,
"properties": [
{
"name": "oauthVerifier",
"value": "{openAuthPopup.code}"
}
]
}
]
},
{
"step": 6,
"type": "mutation",
"name": "verifyConnection",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
}
]
}
]
}

View File

@@ -0,0 +1 @@
export default [];

View File

@@ -1,35 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="256px" height="351px" viewBox="0 0 256 351" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
<defs>
<path d="M1.25273437,280.731641 L2.85834533,277.600858 L102.211177,89.0833546 L58.0613266,5.6082033 C54.3920011,-1.28304578 45.0741245,0.473674398 43.8699203,8.18789086 L1.25273437,280.731641 Z" id="path-1"></path>
<filter x="-50%" y="-50%" width="200%" height="200%" filterUnits="objectBoundingBox" id="filter-2">
<feGaussianBlur stdDeviation="17.5" in="SourceAlpha" result="shadowBlurInner1"></feGaussianBlur>
<feOffset dx="0" dy="0" in="shadowBlurInner1" result="shadowOffsetInner1"></feOffset>
<feComposite in="shadowOffsetInner1" in2="SourceAlpha" operator="arithmetic" k2="-1" k3="1" result="shadowInnerInner1"></feComposite>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.06 0" type="matrix" in="shadowInnerInner1"></feColorMatrix>
</filter>
<path d="M134.417103,148.974235 L166.455722,116.161738 L134.417104,55.1546874 C131.374828,49.3635911 123.983911,48.7568362 120.973828,54.5646483 L103.26875,88.6738296 L102.739423,90.4175473 L134.417103,148.974235 Z" id="path-3"></path>
<filter x="-50%" y="-50%" width="200%" height="200%" filterUnits="objectBoundingBox" id="filter-4">
<feGaussianBlur stdDeviation="3.5" in="SourceAlpha" result="shadowBlurInner1"></feGaussianBlur>
<feOffset dx="1" dy="-9" in="shadowBlurInner1" result="shadowOffsetInner1"></feOffset>
<feComposite in="shadowOffsetInner1" in2="SourceAlpha" operator="arithmetic" k2="-1" k3="1" result="shadowInnerInner1"></feComposite>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.09 0" type="matrix" in="shadowInnerInner1"></feColorMatrix>
</filter>
</defs>
<g>
<path d="M0,282.99762 L2.12250746,280.0256 L102.527363,89.5119284 L102.739423,87.4951323 L58.478806,4.35817711 C54.7706269,-2.60604179 44.3313035,-0.845245771 43.1143483,6.95065473 L0,282.99762 Z" fill="#FFC24A"></path>
<g>
<use fill="#FFA712" fill-rule="evenodd" xlink:href="#path-1"></use>
<use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
</g>
<path d="M135.004975,150.380704 L167.960199,116.629461 L134.995423,53.6993114 C131.866109,47.7425353 123.128817,47.7253411 120.032618,53.6993112 L102.421015,87.2880848 L102.421015,90.1487443 L135.004975,150.380704 Z" fill="#F4BD62"></path>
<g>
<use fill="#FFA50E" fill-rule="evenodd" xlink:href="#path-3"></use>
<use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-3"></use>
</g>
<polygon fill="#F6820C" points="0 282.99762 0.962097168 282.030396 4.45771144 280.60956 132.935323 152.60956 134.563025 148.178595 102.513123 87.1048584"></polygon>
<path d="M139.120971,347.551268 L255.395916,282.703666 L222.191698,78.2093373 C221.153051,71.8112478 213.303658,69.2818149 208.724314,73.8694368 L0.000254726368,282.997875 L115.608454,347.545536 C122.914643,351.624979 131.812872,351.62689 139.120971,347.551268" fill="#FDE068"></path>
<path d="M254.354084,282.159837 L221.401937,79.2179369 C220.371175,72.8684188 213.843792,70.2409553 209.299213,74.79375 L1.28945312,282.600785 L115.627825,346.509458 C122.878548,350.557931 131.709226,350.559827 138.961846,346.515146 L254.354084,282.159837 Z" fill="#FCCA3F"></path>
<path d="M139.120907,345.64082 C131.812808,349.716442 122.914579,349.714531 115.60839,345.635089 L0.93134768,282.014551 L0.000191044776,282.997875 L115.60839,347.545536 C122.914579,351.624979 131.812808,351.62689 139.120907,347.551268 L255.395853,282.703666 L255.111196,280.951785 L139.120907,345.64082 Z" fill="#EEAB37"></path>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -1,89 +0,0 @@
import type {
IAuthentication,
IApp,
IField,
IJSONObject,
} from '@automatisch/types';
import { google as GoogleApi } from 'googleapis';
import { OAuth2Client } from 'google-auth-library';
export default class Authentication implements IAuthentication {
appData: IApp;
connectionData: IJSONObject;
client: OAuth2Client;
scopes: string[] = [
'https://www.googleapis.com/auth/datastore',
'https://www.googleapis.com/auth/firebase',
'https://www.googleapis.com/auth/user.emails.read',
'profile',
];
constructor(appData: IApp, connectionData: IJSONObject) {
this.appData = appData;
this.connectionData = connectionData;
this.client = new GoogleApi.auth.OAuth2(
connectionData.consumerKey as string,
connectionData.consumerSecret as string,
this.oauthRedirectUrl
);
GoogleApi.options({ auth: this.client });
}
get oauthRedirectUrl() {
return this.appData.fields.find(
(field: IField) => field.key == 'oAuthRedirectUrl'
).value;
}
async createAuthData() {
const url = this.client.generateAuthUrl({
access_type: 'offline',
scope: this.scopes,
});
return { url };
}
async verifyCredentials() {
const { tokens } = await this.client.getToken(
this.connectionData.oauthVerifier as string
);
this.client.setCredentials(tokens);
const people = GoogleApi.people('v1');
const { data } = await people.people.get({
resourceName: 'people/me',
personFields: 'emailAddresses',
});
const { emailAddresses, resourceName: userId } = data;
const primaryEmailAddress = emailAddresses.find(
(emailAddress) => emailAddress.metadata.primary
);
return {
consumerKey: this.connectionData.consumerKey,
consumerSecret: this.connectionData.consumerSecret,
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token,
tokenType: tokens.token_type,
expiryDate: tokens.expiry_date,
scope: tokens.scope,
screenName: primaryEmailAddress.value,
userId,
};
}
async isStillVerified() {
try {
await this.client.getTokenInfo(this.connectionData.accessToken as string);
return true;
} catch {
return false;
}
}
}

View File

@@ -1,15 +0,0 @@
import Authentication from './authentication';
import {
IService,
IAuthentication,
IApp,
IJSONObject,
} from '@automatisch/types';
export default class Firebase implements IService {
authenticationClient: IAuthentication;
constructor(appData: IApp, connectionData: IJSONObject) {
this.authenticationClient = new Authentication(appData, connectionData);
}
}

View File

@@ -1,219 +0,0 @@
{
"name": "Firebase",
"key": "firebase",
"iconUrl": "{BASE_URL}/apps/firebase/assets/favicon.svg",
"docUrl": "https://automatisch.io/docs/firebase",
"primaryColor": "ffca28",
"supportsConnections": true,
"fields": [
{
"key": "oAuthRedirectUrl",
"label": "OAuth Redirect URL",
"type": "string",
"required": true,
"readOnly": true,
"value": "{WEB_APP_URL}/app/firebase/connections/add",
"placeholder": null,
"description": "When asked to input an OAuth callback or redirect URL in Firebase OAuth, enter the URL above.",
"docUrl": "https://automatisch.io/docs/firebase#oauth-redirect-url",
"clickToCopy": true
},
{
"key": "consumerKey",
"label": "Consumer Key",
"type": "string",
"required": true,
"readOnly": false,
"value": null,
"placeholder": null,
"description": null,
"docUrl": "https://automatisch.io/docs/firebase#consumer-key",
"clickToCopy": false
},
{
"key": "consumerSecret",
"label": "Consumer Secret",
"type": "string",
"required": true,
"readOnly": false,
"value": null,
"placeholder": null,
"description": null,
"docUrl": "https://automatisch.io/docs/firebase#consumer-secret",
"clickToCopy": false
}
],
"authenticationSteps": [
{
"step": 1,
"type": "mutation",
"name": "createConnection",
"arguments": [
{
"name": "key",
"value": "{key}"
},
{
"name": "formattedData",
"value": null,
"properties": [
{
"name": "consumerKey",
"value": "{fields.consumerKey}"
},
{
"name": "consumerSecret",
"value": "{fields.consumerSecret}"
}
]
}
]
},
{
"step": 2,
"type": "mutation",
"name": "createAuthData",
"arguments": [
{
"name": "id",
"value": "{createConnection.id}"
}
]
},
{
"step": 3,
"type": "openWithPopup",
"name": "openAuthPopup",
"arguments": [
{
"name": "url",
"value": "{createAuthData.url}"
}
]
},
{
"step": 4,
"type": "mutation",
"name": "updateConnection",
"arguments": [
{
"name": "id",
"value": "{createConnection.id}"
},
{
"name": "formattedData",
"value": null,
"properties": [
{
"name": "oauthVerifier",
"value": "{openAuthPopup.code}"
}
]
}
]
},
{
"step": 5,
"type": "mutation",
"name": "verifyConnection",
"arguments": [
{
"name": "id",
"value": "{createConnection.id}"
}
]
}
],
"reconnectionSteps": [
{
"step": 1,
"type": "mutation",
"name": "resetConnection",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
}
]
},
{
"step": 2,
"type": "mutation",
"name": "updateConnection",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
},
{
"name": "formattedData",
"value": null,
"properties": [
{
"name": "consumerKey",
"value": "{fields.consumerKey}"
},
{
"name": "consumerSecret",
"value": "{fields.consumerSecret}"
}
]
}
]
},
{
"step": 3,
"type": "mutation",
"name": "createAuthData",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
}
]
},
{
"step": 4,
"type": "openWithPopup",
"name": "openAuthPopup",
"arguments": [
{
"name": "url",
"value": "{createAuthData.url}"
}
]
},
{
"step": 5,
"type": "mutation",
"name": "updateConnection",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
},
{
"name": "formattedData",
"value": null,
"properties": [
{
"name": "oauthVerifier",
"value": "{openAuthPopup.code}"
}
]
}
]
},
{
"step": 6,
"type": "mutation",
"name": "verifyConnection",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
}
]
}
]
}

View File

@@ -0,0 +1,21 @@
import { IField, IGlobalVariable } from '@automatisch/types';
import { URLSearchParams } from 'url';
export default async function generateAuthUrl($: IGlobalVariable) {
const oauthRedirectUrlField = $.app.auth.fields.find(
(field: IField) => field.key == 'oAuthRedirectUrl'
);
const callbackUrl = oauthRedirectUrlField.value;
const requestPath = '/oauth/request_token';
const data = { oauth_callback: callbackUrl };
const response = await $.http.post(requestPath, data);
const responseData = Object.fromEntries(new URLSearchParams(response.data));
await $.auth.set({
url: `${$.app.apiBaseUrl}/oauth/authorize?oauth_token=${responseData.oauth_token}&perms=delete`,
accessToken: responseData.oauth_token,
accessSecret: responseData.oauth_token_secret,
});
}

View File

@@ -0,0 +1,48 @@
import generateAuthUrl from './generate-auth-url';
import verifyCredentials from './verify-credentials';
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/flickr/connections/add',
placeholder: null,
description:
'When asked to input an OAuth callback or redirect URL in Flickr OAuth, enter the URL above.',
docUrl: 'https://automatisch.io/docs/flickr#oauth-redirect-url',
clickToCopy: true,
},
{
key: 'consumerKey',
label: 'Consumer Key',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
docUrl: 'https://automatisch.io/docs/flickr#consumer-key',
clickToCopy: false,
},
{
key: 'consumerSecret',
label: 'Consumer Secret',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
docUrl: 'https://automatisch.io/docs/flickr#consumer-secret',
clickToCopy: false,
},
],
generateAuthUrl,
verifyCredentials,
isStillVerified,
};

View File

@@ -0,0 +1,13 @@
import { IGlobalVariable } from '@automatisch/types';
const isStillVerified = async ($: IGlobalVariable) => {
const params = {
method: 'flickr.test.login',
format: 'json',
nojsoncallback: 1,
};
const response = await $.http.get('/rest', { params });
return !!response.data.user.id;
};
export default isStillVerified;

View File

@@ -0,0 +1,22 @@
import { IGlobalVariable } from '@automatisch/types';
import { URLSearchParams } from 'url';
const verifyCredentials = async ($: IGlobalVariable) => {
const response = await $.http.post(
`/oauth/access_token?oauth_verifier=${$.auth.data.oauth_verifier}&oauth_token=${$.auth.data.accessToken}`,
null
);
const responseData = Object.fromEntries(new URLSearchParams(response.data));
await $.auth.set({
consumerKey: $.auth.data.consumerKey,
consumerSecret: $.auth.data.consumerSecret,
accessToken: responseData.oauth_token,
accessSecret: responseData.oauth_token_secret,
userId: responseData.user_nsid,
screenName: responseData.fullname,
});
};
export default verifyCredentials;

View File

@@ -1,82 +0,0 @@
import type {
IAuthentication,
IApp,
IField,
IJSONObject,
} from '@automatisch/types';
import FlickrApi from 'flickr-sdk';
export default class Authentication implements IAuthentication {
appData: IApp;
connectionData: IJSONObject;
client: typeof FlickrApi;
oauthClient: typeof FlickrApi;
constructor(appData: IApp, connectionData: IJSONObject) {
this.oauthClient = new FlickrApi.OAuth(
connectionData.consumerKey,
connectionData.consumerSecret
);
if (connectionData.accessToken && connectionData.accessSecret) {
this.client = new FlickrApi(
FlickrApi.OAuth.createPlugin(
connectionData.consumerKey,
connectionData.consumerSecret,
connectionData.accessToken,
connectionData.accessSecret
)
);
}
this.connectionData = connectionData;
this.appData = appData;
}
async createAuthData() {
const appFields = this.appData.fields.find(
(field: IField) => field.key == 'oAuthRedirectUrl'
);
const callbackUrl = appFields.value;
const oauthData = (await this.oauthClient.request(callbackUrl)).body;
const url = await this.oauthClient.authorizeUrl(
oauthData.oauth_token,
'delete'
);
return {
accessToken: oauthData.oauth_token,
accessSecret: oauthData.oauth_token_secret,
url: url,
};
}
async verifyCredentials() {
const verifiedCredentials = (
await this.oauthClient.verify(
this.connectionData.accessToken,
this.connectionData.oauthVerifier,
this.connectionData.accessSecret
)
).body;
return {
consumerKey: this.connectionData.consumerKey,
consumerSecret: this.connectionData.consumerSecret,
accessToken: verifiedCredentials.oauth_token,
accessSecret: verifiedCredentials.oauth_token_secret,
userId: verifiedCredentials.user_nsid,
screenName: verifiedCredentials.fullname,
};
}
async isStillVerified() {
try {
await this.client.test.login();
return true;
} catch {
return false;
}
}
}

View File

@@ -0,0 +1,41 @@
import { Token } from 'oauth-1.0a';
import { IJSONObject, TBeforeRequest } from '@automatisch/types';
import oauthClient from './oauth-client';
type RequestDataType = {
url: string;
method: string;
data?: IJSONObject;
};
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
const { url, method, data, params } = requestConfig;
const token: Token = {
key: $.auth.data?.accessToken as string,
secret: $.auth.data?.accessSecret as string,
};
const requestData: RequestDataType = {
url: `${requestConfig.baseURL}${url}`,
method,
};
if (url === '/oauth/request_token') {
requestData.data = data;
}
if (method === 'get') {
requestData.data = params;
}
const authHeader = oauthClient($).toHeader(
oauthClient($).authorize(requestData, token)
);
requestConfig.headers.Authorization = authHeader.Authorization;
return requestConfig;
};
export default addAuthHeader;

View File

@@ -0,0 +1,23 @@
import { IGlobalVariable } from '@automatisch/types';
import crypto from 'crypto';
import OAuth from 'oauth-1.0a';
const oauthClient = ($: IGlobalVariable) => {
const consumerData = {
key: $.auth.data.consumerKey as string,
secret: $.auth.data.consumerSecret as string,
};
return new OAuth({
consumer: consumerData,
signature_method: 'HMAC-SHA1',
hash_function(base_string, key) {
return crypto
.createHmac('sha1', key)
.update(base_string)
.digest('base64');
},
});
};
export default oauthClient;

View File

@@ -1,10 +0,0 @@
import { IJSONObject } from '@automatisch/types';
import ListAlbums from './data/list-albums';
export default class Data {
listAlbums: ListAlbums;
constructor(connectionData: IJSONObject) {
this.listAlbums = new ListAlbums(connectionData);
}
}

View File

@@ -1,39 +0,0 @@
import FlickrApi from 'flickr-sdk';
import type { IJSONObject } from '@automatisch/types';
export default class ListAlbums {
client?: typeof FlickrApi;
constructor(connectionData: IJSONObject) {
if (
connectionData.consumerKey &&
connectionData.consumerSecret &&
connectionData.accessToken &&
connectionData.accessSecret
) {
this.client = new FlickrApi(
FlickrApi.OAuth.createPlugin(
connectionData.consumerKey,
connectionData.consumerSecret,
connectionData.accessToken,
connectionData.accessSecret
)
);
}
}
async run() {
const { photosets } = (await this.client.photosets.getList()).body;
const allPhotosets = [...photosets.photoset];
for (let page = photosets.page + 1; page <= photosets.pages; page++) {
const { photosets } = (await this.client.photosets.getList({ page, })).body;
allPhotosets.push(...photosets.photoset);
}
return allPhotosets.map((photoset) => ({
value: photoset.id,
name: photoset.title._content,
}));
}
}

View File

@@ -0,0 +1,3 @@
import listAlbums from './list-albums';
export default [listAlbums];

View File

@@ -0,0 +1,56 @@
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
type TResponse = {
data: IJSONObject[];
error?: IJSONObject;
};
type TPhotoset = {
id: string;
title: {
_content: string;
};
};
export default {
name: 'List albums',
key: 'listAlbums',
async run($: IGlobalVariable) {
const params = {
page: 1,
per_page: 500,
user_id: $.auth.data.userId,
method: 'flickr.photosets.getList',
format: 'json',
nojsoncallback: 1,
};
let response = await $.http.get('/rest', { params });
const aggregatedResponse: TResponse = {
data: [...response.data.photosets.photoset],
};
while (response.data.photosets.page < response.data.photosets.pages) {
response = await $.http.get('/rest', {
params: {
...params,
page: response.data.photosets.page,
},
});
aggregatedResponse.data.push(...response.data.photosets.photoset);
}
aggregatedResponse.data = aggregatedResponse.data.map(
(photoset: TPhotoset) => {
return {
value: photoset.id,
name: photoset.title._content,
} as IJSONObject;
}
);
return aggregatedResponse;
},
};

View File

@@ -1 +0,0 @@
declare module 'flickr-sdk';

View File

@@ -1,25 +1,21 @@
import {
IService,
IAuthentication,
IApp,
IJSONObject,
} from '@automatisch/types';
import Authentication from './authentication';
import Triggers from './triggers';
import Data from './data';
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 class Flickr implements IService {
authenticationClient: IAuthentication;
triggers: Triggers;
data: Data;
constructor(
appData: IApp,
connectionData: IJSONObject,
parameters: IJSONObject
) {
this.authenticationClient = new Authentication(appData, connectionData);
this.data = new Data(connectionData);
this.triggers = new Triggers(connectionData, parameters);
}
}
export default defineApp({
name: 'Flickr',
key: 'flickr',
iconUrl: '{BASE_URL}/apps/flickr/assets/favicon.svg',
authDocUrl: 'https://automatisch.io/docs/apps/flickr/connection',
docUrl: 'https://automatisch.io/docs/flickr',
primaryColor: '000000',
supportsConnections: true,
baseUrl: 'https://www.flickr.com/',
apiBaseUrl: 'https://www.flickr.com/services',
beforeRequest: [addAuthHeader],
auth,
triggers,
dynamicData,
});

View File

@@ -1,304 +0,0 @@
{
"name": "Flickr",
"key": "flickr",
"iconUrl": "{BASE_URL}/apps/flickr/assets/favicon.svg",
"docUrl": "https://automatisch.io/docs/flickr",
"primaryColor": "000000",
"supportsConnections": true,
"fields": [
{
"key": "oAuthRedirectUrl",
"label": "OAuth Redirect URL",
"type": "string",
"required": true,
"readOnly": true,
"value": "{WEB_APP_URL}/app/flickr/connections/add",
"placeholder": null,
"description": "When asked to input an OAuth callback or redirect URL in Flickr OAuth, enter the URL above.",
"docUrl": "https://automatisch.io/docs/flickr#oauth-redirect-url",
"clickToCopy": true
},
{
"key": "consumerKey",
"label": "Consumer Key",
"type": "string",
"required": true,
"readOnly": false,
"value": null,
"placeholder": null,
"description": null,
"docUrl": "https://automatisch.io/docs/flickr#consumer-key",
"clickToCopy": false
},
{
"key": "consumerSecret",
"label": "Consumer Secret",
"type": "string",
"required": true,
"readOnly": false,
"value": null,
"placeholder": null,
"description": null,
"docUrl": "https://automatisch.io/docs/flickr#consumer-secret",
"clickToCopy": false
}
],
"authenticationSteps": [
{
"step": 1,
"type": "mutation",
"name": "createConnection",
"arguments": [
{
"name": "key",
"value": "{key}"
},
{
"name": "formattedData",
"value": null,
"properties": [
{
"name": "consumerKey",
"value": "{fields.consumerKey}"
},
{
"name": "consumerSecret",
"value": "{fields.consumerSecret}"
}
]
}
]
},
{
"step": 2,
"type": "mutation",
"name": "createAuthData",
"arguments": [
{
"name": "id",
"value": "{createConnection.id}"
}
]
},
{
"step": 3,
"type": "openWithPopup",
"name": "openAuthPopup",
"arguments": [
{
"name": "url",
"value": "{createAuthData.url}"
}
]
},
{
"step": 4,
"type": "mutation",
"name": "updateConnection",
"arguments": [
{
"name": "id",
"value": "{createConnection.id}"
},
{
"name": "formattedData",
"value": null,
"properties": [
{
"name": "oauthVerifier",
"value": "{openAuthPopup.oauth_verifier}"
}
]
}
]
},
{
"step": 5,
"type": "mutation",
"name": "verifyConnection",
"arguments": [
{
"name": "id",
"value": "{createConnection.id}"
}
]
}
],
"reconnectionSteps": [
{
"step": 1,
"type": "mutation",
"name": "resetConnection",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
}
]
},
{
"step": 2,
"type": "mutation",
"name": "updateConnection",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
},
{
"name": "formattedData",
"value": null,
"properties": [
{
"name": "consumerKey",
"value": "{fields.consumerKey}"
},
{
"name": "consumerSecret",
"value": "{fields.consumerSecret}"
}
]
}
]
},
{
"step": 3,
"type": "mutation",
"name": "createAuthData",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
}
]
},
{
"step": 4,
"type": "openWithPopup",
"name": "openAuthPopup",
"arguments": [
{
"name": "url",
"value": "{createAuthData.url}"
}
]
},
{
"step": 5,
"type": "mutation",
"name": "updateConnection",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
},
{
"name": "formattedData",
"value": null,
"properties": [
{
"name": "oauthVerifier",
"value": "{openAuthPopup.oauth_verifier}"
}
]
}
]
},
{
"step": 6,
"type": "mutation",
"name": "verifyConnection",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
}
]
}
],
"triggers": [
{
"name": "New favorite photo",
"key": "newFavoritePhoto",
"description": "Triggers when you favorite a photo.",
"substeps": [
{
"key": "chooseConnection",
"name": "Choose connection"
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New photo in album",
"key": "newPhotoInAlbum",
"description": "Triggers when you add a new photo in an album.",
"substeps": [
{
"key": "chooseConnection",
"name": "Choose connection"
},
{
"key": "chooseTrigger",
"name": "Set up a trigger",
"arguments": [
{
"label": "Album",
"key": "album",
"type": "dropdown",
"required": true,
"variables": false,
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listAlbums"
}
]
}
}
]
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New photo",
"key": "newPhoto",
"description": "Triggers when you add a new photo.",
"substeps": [
{
"key": "chooseConnection",
"name": "Choose connection"
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New album",
"key": "newAlbum",
"description": "Triggers when you create a new album.",
"substeps": [
{
"key": "chooseConnection",
"name": "Choose connection"
},
{
"key": "testStep",
"name": "Test trigger"
}
]
}
]
}

View File

@@ -1,19 +0,0 @@
import { IJSONObject } from '@automatisch/types';
import NewFavoritePhoto from './triggers/new-favorite-photo';
import NewPhotoInAlbum from './triggers/new-photo-in-album';
import NewPhoto from './triggers/new-photo';
import NewAlbum from './triggers/new-album';
export default class Triggers {
newFavoritePhoto: NewFavoritePhoto;
newPhotoInAlbum: NewPhotoInAlbum;
newPhoto: NewPhoto;
newAlbum: NewAlbum;
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
this.newFavoritePhoto = new NewFavoritePhoto(connectionData);
this.newPhotoInAlbum = new NewPhotoInAlbum(connectionData, parameters);
this.newPhoto = new NewPhoto(connectionData);
this.newAlbum = new NewAlbum(connectionData);
}
}

View File

@@ -0,0 +1,6 @@
import newAlbums from './new-albums';
import newFavoritePhotos from './new-favorite-photos';
import newPhotos from './new-photos';
import newPhotosInAlbums from './new-photos-in-album';
export default [newAlbums, newFavoritePhotos, newPhotos, newPhotosInAlbums];

View File

@@ -1,103 +0,0 @@
import { DateTime } from 'luxon';
import FlickrApi from 'flickr-sdk';
import { IJSONObject } from '@automatisch/types';
export default class NewAlbum {
client?: typeof FlickrApi;
connectionData?: IJSONObject;
primaryPhotoExtraFields = [
'description',
'license',
'date_upload',
'date_taken',
'owner_name',
'icon_server',
'original_format',
'last_update',
'geo',
'tags',
'machine_tags',
'o_dims',
'views',
'media',
'path_alias',
'url_sq',
'url_t',
'url_s',
'url_q',
'url_m',
'url_n',
'url_z',
'url_c',
'url_l',
'url_o',
].join(',');
constructor(connectionData: IJSONObject) {
if (
connectionData.consumerKey &&
connectionData.consumerSecret &&
connectionData.accessToken &&
connectionData.accessSecret
) {
this.client = new FlickrApi(
FlickrApi.OAuth.createPlugin(
connectionData.consumerKey,
connectionData.consumerSecret,
connectionData.accessToken,
connectionData.accessSecret
)
);
this.connectionData = connectionData;
}
}
async getAlbums(options: { perPage?: number, page?: number } = {}) {
const { perPage, page } = options;
const payload = {
page,
per_page: perPage,
primary_photo_extras: this.primaryPhotoExtraFields,
};
const { photosets } = (await this.client.photosets.getList(payload)).body;
return photosets;
}
async run(startTime: Date) {
const albums = await this.getAlbums({ page: 1 });
const allAlbums = [...albums.photoset];
const newAlbums = [];
let page = 1;
for (const album of allAlbums) {
const createdAtInSeconds = DateTime.fromSeconds(parseInt(album.date_create, 10));
const createdAt = createdAtInSeconds.toMillis();
if (createdAt <= startTime.getTime()) {
break;
}
newAlbums.push(album);
const currentIndex = allAlbums.indexOf(album);
const totalAlbums = allAlbums.length;
const isLastItem = currentIndex + 1 === totalAlbums;
if (isLastItem && page < albums.pages) {
page = page + 1;
const { photoset } = await this.getAlbums({ page, });
allAlbums.push(...photoset.photoset);
}
}
return newAlbums;
}
async testRun() {
const { photoset } = await this.getAlbums({ perPage: 1 });
return photoset;
}
}

View File

@@ -0,0 +1,13 @@
import defineTrigger from '../../../../helpers/define-trigger';
import newAlbums from './new-albums';
export default defineTrigger({
name: 'New albums',
pollInterval: 15,
key: 'newAlbums',
description: 'Triggers when you create a new album.',
async run($) {
await newAlbums($);
},
});

View File

@@ -0,0 +1,55 @@
import { IGlobalVariable } from '@automatisch/types';
const extraFields = [
'license',
'date_upload',
'date_taken',
'owner_name',
'icon_server',
'original_format',
'last_update',
'geo',
'tags',
'machine_tags',
'o_dims',
'views',
'media',
'path_alias',
'url_sq',
'url_t',
'url_s',
'url_m',
'url_o',
].join(',');
const newAlbums = async ($: IGlobalVariable) => {
let page = 1;
let pages = 1;
do {
const params = {
page,
per_page: 500,
user_id: $.auth.data.userId,
extras: extraFields,
method: 'flickr.photosets.getList',
format: 'json',
nojsoncallback: 1,
};
const response = await $.http.get('/rest', { params });
const photosets = response.data.photosets;
page = photosets.page + 1;
pages = photosets.pages;
for (const photoset of photosets.photoset) {
$.pushTriggerItem({
raw: photoset,
meta: {
internalId: photoset.id as string,
},
});
}
} while (page <= pages);
};
export default newAlbums;

View File

@@ -1,62 +0,0 @@
import { DateTime } from 'luxon';
import FlickrApi from 'flickr-sdk';
import { IJSONObject } from '@automatisch/types';
export default class NewFavoritePhoto {
client?: typeof FlickrApi;
constructor(connectionData: IJSONObject) {
if (
connectionData.consumerKey &&
connectionData.consumerSecret &&
connectionData.accessToken &&
connectionData.accessSecret
) {
this.client = new FlickrApi(
FlickrApi.OAuth.createPlugin(
connectionData.consumerKey,
connectionData.consumerSecret,
connectionData.accessToken,
connectionData.accessSecret
)
);
}
}
async run(startTime: Date) {
const { photos } = (await this.client.favorites.getList()).body;
const favPhotos = [...photos.photo];
const newFavPhotos = [];
let page = 1;
for (const photo of favPhotos) {
const markedFavoriteAt = DateTime.fromSeconds(parseInt(photo.date_faved, 10));
const markedFavoriteAtInMillis = markedFavoriteAt.toMillis();
if (markedFavoriteAtInMillis <= startTime.getTime()) {
break;
}
newFavPhotos.push(photo);
const currentIndex = favPhotos.indexOf(photo);
const totalFavPhotos = favPhotos.length;
const isLastItem = currentIndex + 1 === totalFavPhotos;
if (isLastItem && page < photos.pages) {
page = page + 1;
const { photos } = (await this.client.favorites.getList({ page, })).body;
favPhotos.push(...photos.photo);
}
}
return newFavPhotos;
}
async testRun() {
const { photos } = (await this.client.favorites.getList({ per_page: 1, })).body;
return photos.photo;
}
}

View File

@@ -0,0 +1,13 @@
import defineTrigger from '../../../../helpers/define-trigger';
import newFavoritePhotos from './new-favorite-photos';
export default defineTrigger({
name: 'New favorite photos',
pollInterval: 15,
key: 'newFavoritePhotos',
description: 'Triggers when you favorite a photo.',
async run($) {
await newFavoritePhotos($);
},
});

View File

@@ -0,0 +1,61 @@
import { IGlobalVariable } from '@automatisch/types';
const extraFields = [
'description',
'license',
'date_upload',
'date_taken',
'owner_name',
'icon_server',
'original_format',
'last_update',
'geo',
'tags',
'machine_tags',
'o_dims',
'views',
'media',
'path_alias',
'url_sq',
'url_t',
'url_s',
'url_q',
'url_m',
'url_n',
'url_z',
'url_c',
'url_l',
'url_o',
].join(',');
const newPhotos = async ($: IGlobalVariable) => {
let page = 1;
let pages = 1;
do {
const params = {
page,
per_page: 500,
user_id: $.auth.data.userId,
extras: extraFields,
method: 'flickr.favorites.getList',
format: 'json',
nojsoncallback: 1,
};
const response = await $.http.get('/rest', { params });
const photos = response.data.photos;
page = photos.page + 1;
pages = photos.pages;
for (const photo of photos.photo) {
$.pushTriggerItem({
raw: photo,
meta: {
internalId: photo.date_faved as string,
},
});
}
} while (page <= pages);
};
export default newPhotos;

View File

@@ -1,80 +0,0 @@
import FlickrApi from 'flickr-sdk';
import { IJSONObject } from '@automatisch/types';
export default class NewPhotoInAlbum {
client?: typeof FlickrApi;
connectionData?: IJSONObject;
albumId?: string;
extraFields = [
'license',
'date_upload',
'date_taken',
'owner_name',
'icon_server',
'original_format',
'last_update',
'geo',
'tags',
'machine_tags',
'o_dims',
'views',
'media',
'path_alias',
'url_sq',
'url_t',
'url_s',
'url_m',
'url_o'
].join(',');
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
if (
connectionData.consumerKey &&
connectionData.consumerSecret &&
connectionData.accessToken &&
connectionData.accessSecret
) {
this.client = new FlickrApi(
FlickrApi.OAuth.createPlugin(
connectionData.consumerKey,
connectionData.consumerSecret,
connectionData.accessToken,
connectionData.accessSecret
)
);
this.connectionData = connectionData;
}
if (parameters?.album) {
this.albumId = parameters.album as string;
}
}
async getAlbumPhotos(options: { perPage?: number, page?: number } = {}) {
const { perPage, page } = options;
const payload = {
page,
per_page: perPage,
photoset_id: this.albumId,
user_id: this.connectionData.userId,
extras: this.extraFields,
};
const { photoset } = (await this.client.photosets.getPhotos(payload)).body;
return photoset;
}
async run() {
// TODO: implement pagination on undated entries
const { photo } = await this.getAlbumPhotos({ page: 1 });
return photo;
}
async testRun() {
const { photo } = await this.getAlbumPhotos({ perPage: 1 });
return photo;
}
}

View File

@@ -1,88 +0,0 @@
import { DateTime } from 'luxon';
import FlickrApi from 'flickr-sdk';
import { IJSONObject } from '@automatisch/types';
export default class NewPhoto {
client?: typeof FlickrApi;
connectionData?: IJSONObject;
extraFields = [
'description',
'license',
'date_upload',
'date_taken',
'owner_name',
'icon_server',
'original_format',
'last_update',
'geo',
'tags',
'machine_tags',
'o_dims',
'views',
'media',
'path_alias',
'url_sq',
'url_t',
'url_s',
'url_q',
'url_m',
'url_n',
'url_z',
'url_c',
'url_l',
'url_o',
].join(',');
constructor(connectionData: IJSONObject) {
if (
connectionData.consumerKey &&
connectionData.consumerSecret &&
connectionData.accessToken &&
connectionData.accessSecret
) {
this.client = new FlickrApi(
FlickrApi.OAuth.createPlugin(
connectionData.consumerKey,
connectionData.consumerSecret,
connectionData.accessToken,
connectionData.accessSecret
)
);
this.connectionData = connectionData;
}
}
async getPhotos(options: { perPage?: number, page?: number, minUploadDate?: string } = {}) {
const { perPage, page, minUploadDate } = options;
const payload = {
page,
per_page: perPage,
user_id: this.connectionData.userId,
extras: this.extraFields,
min_upload_date: minUploadDate,
};
const { photos } = (await this.client.photos.search(payload)).body;
return photos;
}
async run(startTime: Date) {
const minUploadDate = DateTime.fromJSDate(startTime).toSeconds().toString();
const photos = await this.getPhotos({ page: 1, minUploadDate });
const allPhotos = [...photos.photo];
for (let page = photos.page + 1; page <= photos.pages; page++) {
const photos = (await this.getPhotos({ page, minUploadDate }));
allPhotos.push(...photos.photo);
}
return allPhotos;
}
async testRun(startTime: Date) {
const { photo } = await this.getPhotos({ perPage: 1 });
return photo;
}
}

View File

@@ -0,0 +1,32 @@
import defineTrigger from '../../../../helpers/define-trigger';
import newPhotosInAlbum from './new-photos-in-album';
export default defineTrigger({
name: 'New photos in album',
pollInterval: 15,
key: 'newPhotosInAlbum',
description: 'Triggers when you add a new photo in an album.',
arguments: [
{
label: 'Album',
key: 'album',
type: 'dropdown' as const,
required: true,
variables: false,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listAlbums',
},
],
},
},
],
async run($) {
await newPhotosInAlbum($);
},
});

View File

@@ -0,0 +1,56 @@
import { IGlobalVariable } from '@automatisch/types';
const extraFields = [
'license',
'date_upload',
'date_taken',
'owner_name',
'icon_server',
'original_format',
'last_update',
'geo',
'tags',
'machine_tags',
'o_dims',
'views',
'media',
'path_alias',
'url_sq',
'url_t',
'url_s',
'url_m',
'url_o',
].join(',');
const newPhotosInAlbum = async ($: IGlobalVariable) => {
let page = 1;
let pages = 1;
do {
const params = {
page,
per_page: 11,
user_id: $.auth.data.userId,
extras: extraFields,
photoset_id: $.step.parameters.album as string,
method: 'flickr.photosets.getPhotos',
format: 'json',
nojsoncallback: 1,
};
const response = await $.http.get('/rest', { params });
const photoset = response.data.photoset;
page = photoset.page + 1;
pages = photoset.pages;
for (const photo of photoset.photo) {
$.pushTriggerItem({
raw: photo,
meta: {
internalId: photo.id as string,
},
});
}
} while (page <= pages);
};
export default newPhotosInAlbum;

View File

@@ -0,0 +1,13 @@
import defineTrigger from '../../../../helpers/define-trigger';
import newPhotos from './new-photos';
export default defineTrigger({
name: 'New photos',
pollInterval: 15,
key: 'newPhotos',
description: 'Triggers when you add a new photo.',
async run($) {
await newPhotos($);
},
});

View File

@@ -0,0 +1,61 @@
import { IGlobalVariable } from '@automatisch/types';
const extraFields = [
'description',
'license',
'date_upload',
'date_taken',
'owner_name',
'icon_server',
'original_format',
'last_update',
'geo',
'tags',
'machine_tags',
'o_dims',
'views',
'media',
'path_alias',
'url_sq',
'url_t',
'url_s',
'url_q',
'url_m',
'url_n',
'url_z',
'url_c',
'url_l',
'url_o',
].join(',');
const newPhotos = async ($: IGlobalVariable) => {
let page = 1;
let pages = 1;
do {
const params = {
page,
per_page: 500,
user_id: $.auth.data.userId,
extras: extraFields,
method: 'flickr.photos.search',
format: 'json',
nojsoncallback: 1,
};
const response = await $.http.get('/rest', { params });
const photos = response.data.photos;
page = photos.page + 1;
pages = photos.pages;
for (const photo of photos.photo) {
$.pushTriggerItem({
raw: photo,
meta: {
internalId: photo.id as string,
},
});
}
} while (page <= pages);
};
export default newPhotos;

View File

@@ -0,0 +1,58 @@
import defineAction from '../../../../helpers/define-action';
import getRepoOwnerAndRepo from '../../common/get-repo-owner-and-repo';
export default defineAction({
name: 'Create issue',
key: 'createIssue',
description: 'Creates a new issue.',
arguments: [
{
label: 'Repo',
key: 'repo',
type: 'dropdown' as const,
required: false,
variables: false,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listRepos',
},
],
},
},
{
label: 'Title',
key: 'title',
type: 'string' as const,
required: true,
variables: true,
},
{
label: 'Body',
key: 'body',
type: 'string' as const,
required: true,
variables: true,
},
],
async run($) {
const repoParameter = $.step.parameters.repo as string;
const title = $.step.parameters.title as string;
const body = $.step.parameters.body as string;
if (!repoParameter) throw new Error('A repo must be set!');
if (!title) throw new Error('A title must be set!');
const { repoOwner, repo } = getRepoOwnerAndRepo(repoParameter);
const response = await $.http.post(`/repos/${repoOwner}/${repo}/issues`, {
title,
body,
});
$.setActionItem({ raw: response.data });
},
});

View File

@@ -0,0 +1,3 @@
import createIssue from './create-issue';
export default [createIssue];

View File

@@ -0,0 +1,23 @@
import { IField, IGlobalVariable } from '@automatisch/types';
import { URLSearchParams } from 'url';
export default async function generateAuthUrl($: IGlobalVariable) {
const scopes = ['read:org', 'repo', 'user'];
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.consumerKey as string,
redirect_uri: redirectUri,
scope: scopes.join(','),
});
const url = `${
$.app.baseUrl
}/login/oauth/authorize?${searchParams.toString()}`;
await $.auth.set({
url,
});
}

View File

@@ -0,0 +1,49 @@
import generateAuthUrl from './generate-auth-url';
import verifyCredentials from './verify-credentials';
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/github/connections/add',
placeholder: null,
description:
'When asked to input an OAuth callback or redirect URL in Github OAuth, enter the URL above.',
docUrl: 'https://automatisch.io/docs/github#oauth-redirect-url',
clickToCopy: true,
},
{
key: 'consumerKey',
label: 'Client ID',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
docUrl: 'https://automatisch.io/docs/github#client-id',
clickToCopy: false,
},
{
key: 'consumerSecret',
label: 'Client Secret',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
docUrl: 'https://automatisch.io/docs/github#client-secret',
clickToCopy: false,
},
],
generateAuthUrl,
verifyCredentials,
isStillVerified,
};

View File

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

View File

@@ -0,0 +1,36 @@
import { IGlobalVariable } from '@automatisch/types';
import getCurrentUser from '../common/get-current-user';
const verifyCredentials = async ($: IGlobalVariable) => {
const response = await $.http.post(
`${$.app.baseUrl}/login/oauth/access_token`,
{
client_id: $.auth.data.consumerKey,
client_secret: $.auth.data.consumerSecret,
code: $.auth.data.code,
},
{
headers: {
Accept: 'application/json',
},
}
);
const data = response.data;
$.auth.data.accessToken = data.access_token;
const currentUser = await getCurrentUser($);
await $.auth.set({
consumerKey: $.auth.data.consumerKey,
consumerSecret: $.auth.data.consumerSecret,
accessToken: data.access_token,
scope: data.scope,
tokenType: data.token_type,
userId: currentUser.id,
screenName: currentUser.login,
});
};
export default verifyCredentials;

View File

@@ -1,95 +0,0 @@
import type {
IAuthentication,
IApp,
IField,
IJSONObject,
} from '@automatisch/types';
import createHttpClient, { IHttpClient } from '../../helpers/http-client';
import { URLSearchParams } from 'url';
export default class Authentication implements IAuthentication {
appData: IApp;
connectionData: IJSONObject;
scopes: string[] = ['read:org', 'repo', 'user'];
client: IHttpClient;
constructor(appData: IApp, connectionData: IJSONObject) {
this.connectionData = connectionData;
this.appData = appData;
this.client = createHttpClient({ baseURL: 'https://github.com' });
}
get oauthRedirectUrl(): string {
return this.appData.fields.find(
(field: IField) => field.key == 'oAuthRedirectUrl'
).value;
}
async createAuthData(): Promise<{ url: string }> {
const searchParams = new URLSearchParams({
client_id: this.connectionData.consumerKey as string,
redirect_uri: this.oauthRedirectUrl,
scope: this.scopes.join(','),
});
const url = `https://github.com/login/oauth/authorize?${searchParams.toString()}`;
return {
url,
};
}
async verifyCredentials() {
const response = await this.client.post('/login/oauth/access_token', {
client_id: this.connectionData.consumerKey,
client_secret: this.connectionData.consumerSecret,
code: this.connectionData.oauthVerifier,
});
const data = Object.fromEntries(new URLSearchParams(response.data));
this.connectionData.accessToken = data.access_token;
const tokenInfo = await this.getTokenInfo();
return {
consumerKey: this.connectionData.consumerKey,
consumerSecret: this.connectionData.consumerSecret,
accessToken: data.access_token,
scope: data.scope,
tokenType: data.token_type,
userId: tokenInfo.data.user.id,
screenName: tokenInfo.data.user.login,
};
}
async getTokenInfo() {
const basicAuthToken = Buffer.from(
this.connectionData.consumerKey + ':' + this.connectionData.consumerSecret
).toString('base64');
const headers = {
Authorization: `Basic ${basicAuthToken}`,
};
const body = {
access_token: this.connectionData.accessToken,
};
return await this.client.post(
`https://api.github.com/applications/${this.connectionData.consumerKey}/token`,
body,
{ headers }
);
}
async isStillVerified() {
try {
await this.getTokenInfo();
return true;
} catch {
return false;
}
}
}

View File

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

View File

@@ -0,0 +1,10 @@
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
const getCurrentUser = async ($: IGlobalVariable): Promise<IJSONObject> => {
const response = await $.http.get('/user');
const currentUser = response.data;
return currentUser;
};
export default getCurrentUser;

View File

@@ -0,0 +1,17 @@
type TRepoOwnerAndRepo = {
repoOwner?: string;
repo?: string;
};
export default function getRepoOwnerAndRepo(
repoFullName: string
): TRepoOwnerAndRepo {
if (!repoFullName) return {};
const [repoOwner, repo] = repoFullName.split('/');
return {
repoOwner,
repo,
};
}

View File

@@ -0,0 +1,32 @@
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
import type { AxiosResponse } from 'axios';
import parseLinkHeader from '../../../helpers/parse-header-link';
type TResponse = {
data: IJSONObject[];
error?: IJSONObject;
};
export default async function paginateAll(
$: IGlobalVariable,
request: Promise<AxiosResponse>
) {
const response = await request;
const aggregatedResponse: TResponse = {
data: [...response.data],
};
let links = parseLinkHeader(response.headers.link);
while (links.next) {
const nextPageResponse = await $.http.request({
...response.config,
url: links.next.uri,
});
aggregatedResponse.data.push(...nextPageResponse.data);
links = parseLinkHeader(nextPageResponse.headers.link);
}
return aggregatedResponse;
}

View File

@@ -1,16 +0,0 @@
import { IJSONObject } from '@automatisch/types';
import ListRepos from './data/list-repos';
import ListBranches from './data/list-branches';
import ListLabels from './data/list-labels';
export default class Data {
listRepos: ListRepos;
listBranches: ListBranches;
listLabels: ListLabels;
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
this.listRepos = new ListRepos(connectionData);
this.listBranches = new ListBranches(connectionData, parameters);
this.listLabels = new ListLabels(connectionData, parameters);
}
}

View File

@@ -1,36 +0,0 @@
import { Octokit } from 'octokit';
import type { IJSONObject } from '@automatisch/types';
import { assignOwnerAndRepo } from '../utils';
export default class ListBranches {
client?: Octokit;
repoOwner?: string;
repo?: string;
constructor(connectionData: IJSONObject, parameters?: IJSONObject) {
if (connectionData.accessToken) {
this.client = new Octokit({
auth: connectionData.accessToken as string,
});
}
assignOwnerAndRepo(this, parameters?.repo as string);
}
get options() {
return {
owner: this.repoOwner,
repo: this.repo,
};
}
async run() {
const branches = await this.client.paginate(this.client.rest.repos.listBranches, this.options);
return branches.map((branch) => ({
value: branch.name,
name: branch.name,
}));
}
}

View File

@@ -1,36 +0,0 @@
import { Octokit } from 'octokit';
import type { IJSONObject } from '@automatisch/types';
import { assignOwnerAndRepo } from '../utils';
export default class ListLabels {
client?: Octokit;
repoOwner?: string;
repo?: string;
constructor(connectionData: IJSONObject, parameters?: IJSONObject) {
if (connectionData.accessToken) {
this.client = new Octokit({
auth: connectionData.accessToken as string,
});
}
assignOwnerAndRepo(this, parameters?.repo as string);
}
get options() {
return {
owner: this.repoOwner,
repo: this.repo,
};
}
async run() {
const labels = await this.client.paginate(this.client.rest.issues.listLabelsForRepo, this.options);
return labels.map((label) => ({
value: label.name,
name: label.name,
}));
}
}

View File

@@ -1,23 +0,0 @@
import { Octokit } from 'octokit';
import type { IJSONObject } from '@automatisch/types';
export default class ListRepos {
client?: Octokit;
constructor(connectionData: IJSONObject) {
if (connectionData.accessToken) {
this.client = new Octokit({
auth: connectionData.accessToken as string,
});
}
}
async run() {
const repos = await this.client.paginate(this.client.rest.repos.listForAuthenticatedUser);
return repos.map((repo) => ({
value: repo.full_name,
name: repo.full_name,
}));
}
}

View File

@@ -0,0 +1,4 @@
import listLabels from './list-labels';
import listRepos from './list-repos';
export default [listLabels, listRepos];

View File

@@ -0,0 +1,28 @@
import { IGlobalVariable } from '@automatisch/types';
import getRepoOwnerAndRepo from '../../common/get-repo-owner-and-repo';
import paginateAll from '../../common/paginate-all';
export default {
name: 'List labels',
key: 'listLabels',
async run($: IGlobalVariable) {
const { repoOwner, repo } = getRepoOwnerAndRepo(
$.step.parameters.repo as string
);
if (!repo) return { data: [] };
const firstPageRequest = $.http.get(`/repos/${repoOwner}/${repo}/labels`);
const response = await paginateAll($, firstPageRequest);
response.data = response.data.map((repo: { name: string }) => {
return {
value: repo.name,
name: repo.name,
};
});
return response;
},
};

View File

@@ -0,0 +1,21 @@
import { IGlobalVariable } from '@automatisch/types';
import paginateAll from '../../common/paginate-all';
export default {
name: 'List repos',
key: 'listRepos',
async run($: IGlobalVariable) {
const firstPageRequest = $.http.get('/user/repos');
const response = await paginateAll($, firstPageRequest);
response.data = response.data.map((repo: { full_name: string }) => {
return {
value: repo.full_name,
name: repo.full_name,
};
});
return response;
},
};

View File

@@ -1,25 +1,22 @@
import {
IService,
IAuthentication,
IApp,
IJSONObject,
} from '@automatisch/types';
import Authentication from './authentication';
import Triggers from './triggers';
import Data from './data';
import defineApp from '../../helpers/define-app';
import addAuthHeader from './common/add-auth-header';
import auth from './auth';
import triggers from './triggers';
import actions from './actions';
import dynamicData from './dynamic-data';
export default class Github implements IService {
authenticationClient: IAuthentication;
triggers: Triggers;
data: Data;
constructor(
appData: IApp,
connectionData: IJSONObject,
parameters: IJSONObject
) {
this.authenticationClient = new Authentication(appData, connectionData);
this.data = new Data(connectionData, parameters);
this.triggers = new Triggers(connectionData, parameters);
}
}
export default defineApp({
name: 'Github',
key: 'github',
baseUrl: 'https://github.com',
apiBaseUrl: 'https://api.github.com',
iconUrl: '{BASE_URL}/apps/github/assets/favicon.svg',
authDocUrl: 'https://automatisch.io/docs/apps/github/connection',
primaryColor: '000000',
supportsConnections: true,
beforeRequest: [addAuthHeader],
auth,
triggers,
actions,
dynamicData,
});

View File

@@ -1,747 +0,0 @@
{
"name": "Github",
"key": "github",
"iconUrl": "{BASE_URL}/apps/github/assets/favicon.svg",
"docUrl": "https://automatisch.io/docs/github",
"primaryColor": "000000",
"supportsConnections": true,
"fields": [
{
"key": "oAuthRedirectUrl",
"label": "OAuth Redirect URL",
"type": "string",
"required": true,
"readOnly": true,
"value": "{WEB_APP_URL}/app/github/connections/add",
"placeholder": null,
"description": "When asked to input an OAuth callback or redirect URL in Github OAuth, enter the URL above.",
"docUrl": "https://automatisch.io/docs/github#oauth-redirect-url",
"clickToCopy": true
},
{
"key": "consumerKey",
"label": "Client ID",
"type": "string",
"required": true,
"readOnly": false,
"value": null,
"placeholder": null,
"description": null,
"docUrl": "https://automatisch.io/docs/github#client-id",
"clickToCopy": false
},
{
"key": "consumerSecret",
"label": "Client Secret",
"type": "string",
"required": true,
"readOnly": false,
"value": null,
"placeholder": null,
"description": null,
"docUrl": "https://automatisch.io/docs/github#client-secret",
"clickToCopy": false
}
],
"authenticationSteps": [
{
"step": 1,
"type": "mutation",
"name": "createConnection",
"arguments": [
{
"name": "key",
"value": "{key}"
},
{
"name": "formattedData",
"value": null,
"properties": [
{
"name": "consumerKey",
"value": "{fields.consumerKey}"
},
{
"name": "consumerSecret",
"value": "{fields.consumerSecret}"
}
]
}
]
},
{
"step": 2,
"type": "mutation",
"name": "createAuthData",
"arguments": [
{
"name": "id",
"value": "{createConnection.id}"
}
]
},
{
"step": 3,
"type": "openWithPopup",
"name": "openAuthPopup",
"arguments": [
{
"name": "url",
"value": "{createAuthData.url}"
}
]
},
{
"step": 4,
"type": "mutation",
"name": "updateConnection",
"arguments": [
{
"name": "id",
"value": "{createConnection.id}"
},
{
"name": "formattedData",
"value": null,
"properties": [
{
"name": "oauthVerifier",
"value": "{openAuthPopup.code}"
}
]
}
]
},
{
"step": 5,
"type": "mutation",
"name": "verifyConnection",
"arguments": [
{
"name": "id",
"value": "{createConnection.id}"
}
]
}
],
"reconnectionSteps": [
{
"step": 1,
"type": "mutation",
"name": "resetConnection",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
}
]
},
{
"step": 2,
"type": "mutation",
"name": "updateConnection",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
},
{
"name": "formattedData",
"value": null,
"properties": [
{
"name": "consumerKey",
"value": "{fields.consumerKey}"
},
{
"name": "consumerSecret",
"value": "{fields.consumerSecret}"
}
]
}
]
},
{
"step": 3,
"type": "mutation",
"name": "createAuthData",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
}
]
},
{
"step": 4,
"type": "openWithPopup",
"name": "openAuthPopup",
"arguments": [
{
"name": "url",
"value": "{createAuthData.url}"
}
]
},
{
"step": 5,
"type": "mutation",
"name": "updateConnection",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
},
{
"name": "formattedData",
"value": null,
"properties": [
{
"name": "oauthVerifier",
"value": "{openAuthPopup.code}"
}
]
}
]
},
{
"step": 6,
"type": "mutation",
"name": "verifyConnection",
"arguments": [
{
"name": "id",
"value": "{connection.id}"
}
]
}
],
"triggers": [
{
"name": "New repository",
"key": "newRepository",
"description": "Triggers when a new repository is created",
"substeps": [
{
"key": "chooseConnection",
"name": "Choose connection"
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New organization",
"key": "newOrganization",
"description": "Triggers when a new organization is created",
"substeps": [
{
"key": "chooseConnection",
"name": "Choose connection"
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New branch",
"key": "newBranch",
"description": "Triggers when a new branch is created",
"substeps": [
{
"key": "chooseConnection",
"name": "Choose connection"
},
{
"key": "chooseTrigger",
"name": "Set up a trigger",
"arguments": [
{
"label": "Repo",
"key": "repo",
"type": "dropdown",
"required": true,
"variables": false,
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listRepos"
}
]
}
}
]
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New notification",
"key": "newNotification",
"description": "Triggers when a new notification is created",
"substeps": [
{
"key": "chooseConnection",
"name": "Choose connection"
},
{
"key": "chooseTrigger",
"name": "Set up a trigger",
"arguments": [
{
"label": "Repo",
"key": "repo",
"type": "dropdown",
"required": false,
"variables": false,
"description": "If blank, we will retrieve all notifications.",
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listRepos"
}
]
}
}
]
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New pull request",
"key": "newPullRequest",
"description": "Triggers when a new pull request is created",
"substeps": [
{
"key": "chooseConnection",
"name": "Choose connection"
},
{
"key": "chooseTrigger",
"name": "Set up a trigger",
"arguments": [
{
"label": "Repo",
"key": "repo",
"type": "dropdown",
"required": true,
"variables": false,
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listRepos"
}
]
}
}
]
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New watcher",
"key": "newWatcher",
"description": "Triggers when a new watcher is added to a repo",
"substeps": [
{
"key": "chooseConnection",
"name": "Choose connection"
},
{
"key": "chooseTrigger",
"name": "Set up a trigger",
"arguments": [
{
"label": "Repo",
"key": "repo",
"type": "dropdown",
"required": true,
"variables": false,
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listRepos"
}
]
}
}
]
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New milestone",
"key": "newMilestone",
"description": "Triggers when a new milestone is created",
"substeps": [
{
"key": "chooseConnection",
"name": "Choose connection"
},
{
"key": "chooseTrigger",
"name": "Set up a trigger",
"arguments": [
{
"label": "Repo",
"key": "repo",
"type": "dropdown",
"required": true,
"variables": false,
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listRepos"
}
]
}
}
]
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New commit comment",
"key": "newCommitComment",
"description": "Triggers when a new commit comment is created",
"substeps": [
{
"key": "chooseConnection",
"name": "Choose connection"
},
{
"key": "chooseTrigger",
"name": "Set up a trigger",
"arguments": [
{
"label": "Repo",
"key": "repo",
"type": "dropdown",
"required": true,
"variables": false,
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listRepos"
}
]
}
}
]
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New label",
"key": "newLabel",
"description": "Triggers when a new label is created",
"substeps": [
{
"key": "chooseConnection",
"name": "Choose connection"
},
{
"key": "chooseTrigger",
"name": "Set up a trigger",
"arguments": [
{
"label": "Repo",
"key": "repo",
"type": "dropdown",
"required": true,
"variables": false,
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listRepos"
}
]
}
}
]
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New collaborator",
"key": "newCollaborator",
"description": "Triggers when a new collaborator is added to a repo",
"substeps": [
{
"key": "chooseConnection",
"name": "Choose connection"
},
{
"key": "chooseTrigger",
"name": "Set up a trigger",
"arguments": [
{
"label": "Repo",
"key": "repo",
"type": "dropdown",
"required": true,
"variables": false,
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listRepos"
}
]
}
}
]
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New release",
"key": "newRelease",
"description": "Triggers when a new release is created",
"substeps": [
{
"key": "chooseConnection",
"name": "Choose connection"
},
{
"key": "chooseTrigger",
"name": "Set up a trigger",
"arguments": [
{
"label": "Repo",
"key": "repo",
"type": "dropdown",
"required": true,
"variables": false,
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listRepos"
}
]
}
}
]
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New commit",
"key": "newCommit",
"description": "Triggers when a new commit is created",
"substeps": [
{
"key": "chooseConnection",
"name": "Choose connection"
},
{
"key": "chooseTrigger",
"name": "Set up a trigger",
"arguments": [
{
"label": "Repo",
"key": "repo",
"type": "dropdown",
"required": true,
"variables": false,
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listRepos"
}
]
}
},
{
"label": "Head",
"key": "head",
"type": "dropdown",
"description": "Branch to pull commits from. If unspecified, will use the repository's default branch (usually main or develop).",
"required": false,
"variables": false,
"dependsOn": ["parameters.repo"],
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listBranches"
},
{
"name": "parameters.repo",
"value": "{parameters.repo}"
}
]
}
}
]
},
{
"key": "testStep",
"name": "Test trigger"
}
]
},
{
"name": "New issue",
"key": "newIssue",
"description": "Triggers when a new issue is created",
"substeps": [
{
"key": "chooseConnection",
"name": "Choose connection"
},
{
"key": "chooseTrigger",
"name": "Set up a trigger",
"arguments": [
{
"label": "Repo",
"key": "repo",
"type": "dropdown",
"required": false,
"variables": false,
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listRepos"
}
]
}
},
{
"label": "Which types of issues should this trigger on?",
"key": "issueType",
"type": "dropdown",
"description": "Defaults to any issue you can see.",
"required": true,
"variables": false,
"value": "all",
"options": [
{
"label": "Any issue you can see",
"value": "all"
},
{
"label": "Only issues assigned to you",
"value": "assigned"
},
{
"label": "Only issues created by you",
"value": "created"
},
{
"label": "Only issues you're mentioned in",
"value": "mentioned"
},
{
"label": "Only issues you're subscribed to",
"value": "subscribed"
}
]
},
{
"label": "Label",
"key": "label",
"type": "dropdown",
"description": "Only trigger on issues when this label is added.",
"required": false,
"variables": false,
"dependsOn": ["parameters.repo"],
"source": {
"type": "query",
"name": "getData",
"arguments": [
{
"name": "key",
"value": "listLabels"
},
{
"name": "parameters.repo",
"value": "{parameters.repo}"
}
]
}
}
]
},
{
"key": "testStep",
"name": "Test trigger"
}
]
}
]
}

View File

@@ -1,46 +0,0 @@
import { IJSONObject } from '@automatisch/types';
import NewRepository from './triggers/new-repository';
import NewOrganization from './triggers/new-organization';
import NewBranch from './triggers/new-branch';
import NewNotification from './triggers/new-notification';
import NewPullRequest from './triggers/new-pull-request';
import NewWatcher from './triggers/new-watcher';
import NewMilestone from './triggers/new-milestone';
import NewCommit from './triggers/new-commit';
import NewCommitComment from './triggers/new-commit-comment';
import NewLabel from './triggers/new-label';
import NewCollaborator from './triggers/new-collaborator';
import NewRelease from './triggers/new-release';
import NewIssue from './triggers/new-issue';
export default class Triggers {
newRepository: NewRepository;
newOrganization: NewOrganization;
newBranch: NewBranch;
newNotification: NewNotification;
newPullRequest: NewPullRequest;
newWatcher: NewWatcher;
newMilestone: NewMilestone;
newCommit: NewCommit;
newCommitComment: NewCommitComment;
newLabel: NewLabel;
newCollaborator: NewCollaborator;
newRelease: NewRelease;
newIssue: NewIssue;
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
this.newRepository = new NewRepository(connectionData);
this.newOrganization = new NewOrganization(connectionData);
this.newBranch = new NewBranch(connectionData, parameters);
this.newNotification = new NewNotification(connectionData, parameters);
this.newPullRequest = new NewPullRequest(connectionData, parameters);
this.newWatcher = new NewWatcher(connectionData, parameters);
this.newMilestone = new NewMilestone(connectionData, parameters);
this.newCommit = new NewCommit(connectionData, parameters);
this.newCommitComment = new NewCommitComment(connectionData, parameters);
this.newLabel = new NewLabel(connectionData, parameters);
this.newCollaborator = new NewCollaborator(connectionData, parameters);
this.newRelease = new NewRelease(connectionData, parameters);
this.newIssue = new NewIssue(connectionData, parameters);
}
}

View File

@@ -0,0 +1,6 @@
import newIssues from './new-issues';
import newPullRequests from './new-pull-requests';
import newStargazers from './new-stargazers';
import newWatchers from './new-watchers';
export default [newIssues, newPullRequests, newStargazers, newWatchers];

View File

@@ -1,42 +0,0 @@
import { Octokit } from 'octokit';
import { IJSONObject } from '@automatisch/types';
import { assignOwnerAndRepo } from '../utils';
export default class NewBranch {
client?: Octokit;
repoOwner?: string;
repo?: string;
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
if (connectionData.accessToken) {
this.client = new Octokit({
auth: connectionData.accessToken as string,
});
}
assignOwnerAndRepo(this, parameters?.repo as string);
}
get options() {
return {
owner: this.repoOwner,
repo: this.repo,
};
}
async run() {
// TODO: implement pagination on undated entires
return await this.client.paginate(this.client.rest.repos.listBranches, this.options);
}
async testRun() {
const options = {
...this.options,
per_page: 1,
};
const { data: branches } = await this.client.rest.repos.listBranches(options);
return branches;
}
}

View File

@@ -1,44 +0,0 @@
import { Octokit } from 'octokit';
import { IJSONObject } from '@automatisch/types';
import { assignOwnerAndRepo } from '../utils';
export default class NewCollaborator {
client?: Octokit;
repoOwner?: string;
repo?: string;
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
if (connectionData.accessToken) {
this.client = new Octokit({
auth: connectionData.accessToken as string,
});
}
assignOwnerAndRepo(this, parameters?.repo as string);
}
get options() {
return {
owner: this.repoOwner,
repo: this.repo,
};
}
async run() {
// TODO: implement pagination on undated entries
return await this.client.paginate(
this.client.rest.repos.listCollaborators,
this.options
);
}
async testRun() {
const options = {
...this.options,
per_page: 1,
};
return (await this.client.rest.repos.listCollaborators(options)).data;
}
}

View File

@@ -1,59 +0,0 @@
import { Octokit } from 'octokit';
import { DateTime } from 'luxon';
import { IJSONObject } from '@automatisch/types';
import { assignOwnerAndRepo } from '../utils';
export default class NewCommitComment {
client?: Octokit;
repoOwner?: string;
repo?: string;
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
if (connectionData.accessToken) {
this.client = new Octokit({
auth: connectionData.accessToken as string,
});
}
assignOwnerAndRepo(this, parameters?.repo as string);
}
get options() {
return {
owner: this.repoOwner,
repo: this.repo,
};
}
async run(startTime: Date) {
const iterator = await this.client.paginate.iterator(this.client.rest.repos.listCommitCommentsForRepo, this.options);
const newCommitComments = [];
const startTimeDateObject = DateTime.fromJSDate(startTime);
commitCommentIterator:
for await (const { data: commitComments } of iterator) {
for (const commitComment of commitComments) {
const createdAtDateObject = DateTime.fromISO(commitComment.created_at);
if (createdAtDateObject < startTimeDateObject) {
break commitCommentIterator;
}
newCommitComments.push(commitComment);
}
}
return newCommitComments;
}
async testRun() {
const options = {
...this.options,
per_page: 1,
};
return (await this.client.rest.repos.listCommitCommentsForRepo(options)).data;
}
}

View File

@@ -1,59 +0,0 @@
import { Octokit } from 'octokit';
import { DateTime } from 'luxon';
import { IJSONObject } from '@automatisch/types';
import { assignOwnerAndRepo } from '../utils';
export default class NewCommit {
client?: Octokit;
repoOwner?: string;
repo?: string;
head?: string;
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
if (connectionData.accessToken) {
this.client = new Octokit({
auth: connectionData.accessToken as string,
});
}
if (parameters?.head) {
this.head = parameters.head as string;
}
assignOwnerAndRepo(this, parameters?.repo as string);
}
get options() {
const options = {
owner: this.repoOwner,
repo: this.repo,
};
if (this.head) {
return {
...options,
sha: this.head,
};
}
return options;
}
async run(startTime: Date) {
const options = {
...this.options,
since: DateTime.fromJSDate(startTime).toISO(),
};
return await this.client.paginate(this.client.rest.repos.listCommits, options);
}
async testRun() {
const options = {
...this.options,
per_page: 1,
};
return (await this.client.rest.repos.listCommits(options)).data;
}
}

View File

@@ -1,88 +0,0 @@
import { Octokit } from 'octokit';
import { DateTime } from 'luxon';
import { IJSONObject } from '@automatisch/types';
import { assignOwnerAndRepo } from '../utils';
export default class NewIssue {
client?: Octokit;
connectionData?: IJSONObject;
repoOwner?: string;
repo?: string;
hasRepo?: boolean;
label?: string;
issueType?: string;
constructor(connectionData: IJSONObject, parameters: IJSONObject) {
if (connectionData.accessToken) {
this.client = new Octokit({
auth: connectionData.accessToken as string,
});
}
assignOwnerAndRepo(this, parameters?.repo as string);
}
get options() {
return {
labels: this.label,
}
}
async listRepoIssues(options = {}, paginate = false) {
const listRepoIssues = this.client.rest.issues.listForRepo;
const extendedOptions = {
...this.options,
repo: this.repo,
owner: this.repoOwner,
filter: this.issueType,
...options,
};
if (paginate) {
return await this.client.paginate(listRepoIssues, extendedOptions);
}
return (await listRepoIssues(extendedOptions)).data;
}
async listIssues(options = {}, paginate = false) {
const listIssues = this.client.rest.issues.listForAuthenticatedUser;
const extendedOptions = {
...this.options,
...options,
};
if (paginate) {
return await this.client.paginate(listIssues, extendedOptions);
}
return (await listIssues(extendedOptions)).data;
}
async run(startTime: Date) {
const options = {
since: DateTime.fromJSDate(startTime).toISO(),
};
if (this.hasRepo) {
return await this.listRepoIssues(options, true);
}
return await this.listIssues(options, true);
}
async testRun() {
const options = {
per_page: 1,
};
if (this.hasRepo) {
return await this.listRepoIssues(options, false);
}
return await this.listIssues(options, false);
}
}

View File

@@ -0,0 +1,86 @@
import defineTrigger from '../../../../helpers/define-trigger';
import newIssues from './new-issues';
export default defineTrigger({
name: 'New issues',
key: 'newIssues',
pollInterval: 15,
description: 'Triggers when a new issue is created',
arguments: [
{
label: 'Repo',
key: 'repo',
type: 'dropdown' as const,
required: false,
variables: false,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listRepos',
},
],
},
},
{
label: 'Which types of issues should this trigger on?',
key: 'issueType',
type: 'dropdown' as const,
description: 'Defaults to any issue you can see.',
required: true,
variables: false,
value: 'all',
options: [
{
label: 'Any issue you can see',
value: 'all',
},
{
label: 'Only issues assigned to you',
value: 'assigned',
},
{
label: 'Only issues created by you',
value: 'created',
},
{
label: `Only issues you're mentioned in`,
value: 'mentioned',
},
{
label: `Only issues you're subscribed to`,
value: 'subscribed',
},
],
},
{
label: 'Label',
key: 'label',
type: 'dropdown' as const,
description: 'Only trigger on issues when this label is added.',
required: false,
variables: false,
dependsOn: ['parameters.repo'],
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listLabels',
},
{
name: 'parameters.repo',
value: '{parameters.repo}',
},
],
},
},
],
async run($) {
await newIssues($);
},
});

View File

@@ -0,0 +1,50 @@
import { IGlobalVariable } from '@automatisch/types';
import getRepoOwnerAndRepo from '../../common/get-repo-owner-and-repo';
import parseLinkHeader from '../../../../helpers/parse-header-link';
function getPathname($: IGlobalVariable) {
const { repoOwner, repo } = getRepoOwnerAndRepo(
$.step.parameters.repo as string
);
if (repoOwner && repo) {
return `/repos/${repoOwner}/${repo}/issues`;
}
return '/issues';
}
const newIssues = async ($: IGlobalVariable) => {
const pathname = getPathname($);
const params = {
labels: $.step.parameters.label,
filter: 'all',
state: 'all',
sort: 'created',
direction: 'desc',
per_page: 100,
};
let links;
do {
const response = await $.http.get(pathname, { params });
links = parseLinkHeader(response.headers.link);
if (response.data.length) {
for (const issue of response.data) {
const issueId = issue.id;
const dataItem = {
raw: issue,
meta: {
internalId: issueId.toString(),
},
};
$.pushTriggerItem(dataItem);
}
}
} while (links.next);
};
export default newIssues;

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