Compare commits

..

151 Commits

Author SHA1 Message Date
Rıdvan Akca
c7604fa785 feat(eventbrite): add updated attendee trigger 2024-06-11 14:19:54 +02:00
Rıdvan Akca
a77ac9a3f9 feat(eventbrite): add new attendee registered trigger 2024-06-11 12:30:37 +02:00
Rıdvan Akca
6062cfafaf feat(eventbrite): add new attendee check in trigger 2024-06-11 12:18:00 +02:00
Rıdvan Akca
5263e774d2 feat(eventbrite): add new events trigger 2024-06-11 11:31:28 +02:00
Rıdvan Akca
22b4a04567 feat(eventbrite): add eventbrite integration 2024-06-11 10:44:29 +02:00
Ali BARIN
e0d610071d Merge pull request #1917 from automatisch/AUT-1009
feat: hide react-flow attribution
2024-06-05 15:35:09 +02:00
kasia.oczkowska
ab0966c005 feat: hide react-flow attribution 2024-06-05 13:39:49 +01:00
Ali BARIN
751eb41e72 Merge pull request #1817 from automatisch/new-editor-feature-flag
feat: introduce feature flag for new flow editor
2024-06-04 12:45:00 +02:00
kasia.oczkowska
f08dc25711 feat: introduce style and behavior improvements 2024-06-04 07:18:18 +00:00
kasia.oczkowska
737eb31776 feat: introduce custom edges, auto layout improvements and node data updates 2024-06-04 07:17:38 +00:00
kasia.oczkowska
d6abf283bc feat: introduce automatic layout for new flow editor 2024-06-04 07:17:38 +00:00
kasia.oczkowska
bac4ab5aa4 feat: introduce feature flag for new flow editor 2024-06-04 07:17:38 +00:00
Ali BARIN
b5839390fd Merge pull request #1911 from automatisch/render-yaml-fix
fix(render.yaml): correct docker contexts
2024-06-03 11:21:31 +02:00
Ali BARIN
d19271dae1 fix(render.yaml): correct docker contexts 2024-06-03 10:23:28 +02:00
Ali BARIN
ef5a09314e Merge pull request #1907 from automatisch/fix-admin-apps-undefined-error
fix(AdminApplicationSettings): handle undefined appConfig object
2024-05-31 13:52:13 +02:00
Rıdvan Akca
ba52e298eb fix(AdminApplicationSettings): handle undefined appConfig object 2024-05-31 13:26:06 +02:00
Ali BARIN
b3c3998189 Merge pull request #1895 from automatisch/fix-appkey-error-in-flowrow
fix: remove unnecessary appKey in FlowRow and FlowContextMenu
2024-05-31 12:54:13 +02:00
Ömer Faruk Aydın
782f9b5c04 Merge pull request #1900 from automatisch/release/v0.12.0
release(v0.12.0): Update version to 0.12.0
2024-05-28 12:05:52 +02:00
Faruk AYDIN
3079d8c605 Update version to 0.12.0 2024-05-28 11:13:54 +02:00
Rıdvan Akca
c5202d7b3e fix: remove unnecessary appKey in FlowRow and FlowContextMenu 2024-05-24 14:11:22 +02:00
Ali BARIN
fbae83f4de Merge pull request #1874 from automatisch/make-value-column-text-in-datastore
fix(datastore): make value column text
2024-05-23 10:13:47 +02:00
Ali BARIN
5b7b8c934f Merge pull request #1890 from automatisch/unix-to-date-format
feat(formatter/format-date-time): add unix to datetime support
2024-05-17 15:15:06 +02:00
Rıdvan Akca
b70223e824 feat(formatter/format-date-time): add unix to datetime support 2024-05-17 15:02:22 +02:00
Ali BARIN
9900bbbc8d Merge pull request #1889 from automatisch/fix-attribute-typo 2024-05-17 14:09:01 +02:00
Rıdvan Akca
fc7f1ddd69 fix(appwrite): fix attribute typo 2024-05-17 13:47:16 +02:00
Ali BARIN
991b2f4bf7 Merge pull request #1887 from automatisch/AUT-982
feat: implement propTypes for FlowSubstep and FlowSubstepTitle
2024-05-17 13:13:03 +02:00
Ali BARIN
bb251b16a9 Merge pull request #1556 from automatisch/AUT-610
feat(appwrite): add appwrite integration
2024-05-15 21:49:23 +02:00
Ali BARIN
1b34a48a61 refactor(appwrite/dynamic-data): use native API ordering 2024-05-15 17:59:56 +00:00
Ali BARIN
8c83b715fe fix(appwrite/new-documents): add native order and pagination support 2024-05-15 17:59:56 +00:00
Ali BARIN
c122708b0b fix(appwrite): utilize DOCS_URL 2024-05-15 17:59:56 +00:00
Ali BARIN
258d920ff2 docs(appwrite): describe project settings and hostname 2024-05-15 17:59:56 +00:00
Rıdvan Akca
6e5c0cc0c7 feat(appwrite): add new documents trigger 2024-05-15 17:59:54 +00:00
Rıdvan Akca
9dc82290b5 feat(appwrite): add appwrite integration 2024-05-15 17:59:39 +00:00
Ali BARIN
bb73f90374 Merge pull request #1888 from automatisch/fix-e2e-tests
test: update first app path
2024-05-15 17:59:59 +02:00
Ali BARIN
8a0720b0e3 test: update first app path 2024-05-15 15:52:02 +00:00
Ali BARIN
88468c4f89 Merge pull request #1551 from automatisch/AUT-599
feat(airtable): add airtable integration
2024-05-15 17:35:55 +02:00
Ali BARIN
d19a45592f docs(airtable): link to airtable for apps 2024-05-15 15:30:48 +00:00
Ali BARIN
21a921d25d fix(airtable): remove user ID out of screen name 2024-05-15 15:30:36 +00:00
Ali BARIN
3b2946aac5 fix(airtable/find-record): make limitToView optional field 2024-05-15 15:30:23 +00:00
Ali BARIN
196d555e8c fix(airtable): utilize DOCS_URL 2024-05-15 15:29:31 +00:00
Ali BARIN
28f39b5c7e Merge pull request #1555 from automatisch/AUT-604
feat(airtable): add find record action
2024-05-15 17:28:38 +02:00
Rıdvan Akca
ec8ac17f4a feat(airtable): add find record action 2024-05-15 15:25:55 +00:00
Ali BARIN
c45573349a Merge pull request #1554 from automatisch/AUT-602
feat(airtable): add create record action
2024-05-15 17:24:58 +02:00
Rıdvan Akca
d36c9d43f6 feat(airtable): add create record action 2024-05-15 17:20:47 +02:00
Rıdvan Akca
b06c744392 feat: implement propTypes for FlowSubstep and FlowSubstepTitle 2024-05-15 13:56:57 +02:00
Ali BARIN
9548c93b4c Merge pull request #1867 from automatisch/custom-user-seed
add POST /api/v1/installation/users to seed user
2024-05-13 16:19:37 +02:00
Ali BARIN
4144944ab2 refactor(migrations): rename installation completed migration 2024-05-13 14:12:46 +00:00
Ali BARIN
46b85519c1 refactor(User/createAdmin): mark installation completed 2024-05-13 14:10:21 +00:00
Ali BARIN
5a83fc33ec refactor(User): rename createAdminUser with createAdmin 2024-05-13 14:09:09 +00:00
Ali BARIN
c80791267f refactor(installation): improve allow installation guard 2024-05-13 14:00:12 +00:00
Ali BARIN
b30f97db3e feat: add POST /api/v1/installation/users to seed user 2024-05-13 13:31:16 +00:00
Ali BARIN
717c81fa2b test(global-hooks): truncate config table 2024-05-13 13:31:16 +00:00
Ali BARIN
ae188bc563 feat: add migration to mark userful instances installation completed 2024-05-13 13:31:16 +00:00
Ali BARIN
fc4561221d feat: add DISABLE_SEED_USER to bypass yarn db:seed:user command 2024-05-13 13:31:16 +00:00
Ali BARIN
5aeb4f8809 Merge pull request #1883 from automatisch/checkisenterprise
feat: remove checkIsEnterprise middleware from admin users
2024-05-13 15:30:55 +02:00
Ali BARIN
c6c900bc39 test(get-users.ee): remove license mock 2024-05-13 13:20:48 +00:00
Rıdvan Akca
c18ab67a25 feat: remove checkIsEnterprise middleware from admin users 2024-05-13 15:02:10 +02:00
Ali BARIN
55ae1470d0 Merge pull request #1875 from automatisch/logout-saml 2024-05-13 13:51:49 +02:00
Ömer Faruk Aydın
a1136fdfb2 Merge pull request #1882 from automatisch/no-proxy
feat: add no_proxy support in http(s) agents
2024-05-13 13:43:39 +02:00
Ali BARIN
dfe56d3aa2 feat: add no_proxy support in http(s) agents 2024-05-13 09:15:37 +00:00
Ali BARIN
3da5e13ecd feat: support bi-directional backchannel SAML SLO 2024-05-10 08:58:32 +00:00
Ali BARIN
40d0fe0db6 chore: add saml_session_id property in access token 2024-05-10 08:54:02 +00:00
Ali BARIN
029fd2d0b0 chore(devcontainer): remove keycloak custom container name 2024-05-10 08:54:02 +00:00
Ali BARIN
12905ad733 Merge pull request #1873 from automatisch/fix-use-dynamic-data
fix(useDynamicData): send correct payload to API
2024-05-10 10:53:14 +02:00
Ali BARIN
d257f59a3e Merge pull request #1872 from automatisch/hide-empty-sso-providers
fix(SsoProviders): hide empty block
2024-05-10 10:53:00 +02:00
Ali BARIN
1dc9646894 fix(datastore): make value column text 2024-05-09 20:31:47 +00:00
Ali BARIN
c9281b4605 fix(useDynamicData): send correct payload to API 2024-05-09 20:08:59 +00:00
Ali BARIN
e9d2ae5d67 fix(SsoProviders): hide empty block 2024-05-09 19:07:30 +00:00
Ali BARIN
0f8e05610b Merge pull request #1482 from automatisch/AUT-508
feat(disqus): add new flagged comments trigger
2024-05-08 15:51:54 +02:00
Ali BARIN
9ea2196e51 refactor(disqus/new-flagged-comments): make filter value variable 2024-05-08 13:48:01 +00:00
Ali BARIN
97327a9033 Merge pull request #1865 from automatisch/AUT-973
refactor(google-tasks/find-task): filter all tasks when finding a task
2024-05-08 15:11:55 +02:00
Ali BARIN
8bf11ba7d1 Merge pull request #1544 from automatisch/AUT-582
feat(ynab): add you need a budget app integration
2024-05-08 15:11:49 +02:00
Ali BARIN
523833b015 Merge pull request #1504 from automatisch/AUT-520
feat(vtiger-crm): add vtiger crm integration
2024-05-08 15:10:16 +02:00
Ali BARIN
e25a651d26 fix(ynab/low-account-balance): scope trigger by year and month 2024-05-08 13:06:40 +00:00
Ali BARIN
92a8c1483d fix(ynab/goal-completed): scope triger by year and month 2024-05-08 12:56:46 +00:00
Ali BARIN
8da3448e9c feat(ynab): utilize DOCS_URL variable 2024-05-08 12:55:37 +00:00
Ali BARIN
f2385d8916 fix(ynab/category-overspent): scope trigger by year and month 2024-05-08 12:54:55 +00:00
Ali BARIN
17a8daa526 Merge pull request #1546 from automatisch/AUT-583
feat(ynab): add low account balance trigger
2024-05-08 12:09:16 +02:00
Ali BARIN
51e254f127 Merge pull request #1547 from automatisch/AUT-584
feat(ynab): add category overspent trigger
2024-05-08 12:09:09 +02:00
Ali BARIN
feccf571cd Merge pull request #1548 from automatisch/AUT-585
feat(ynab): add goal completed trigger
2024-05-08 12:08:57 +02:00
Ali BARIN
9f8ce44c1b Merge pull request #1549 from automatisch/AUT-586
feat(ynab): add new transactions trigger
2024-05-08 12:04:16 +02:00
Ali BARIN
a7cfe7f23b Merge pull request #1866 from automatisch/fix-ynab
refactor(ynab): specify a budget when creating a connection
2024-05-08 12:00:46 +02:00
Rıdvan Akca
8eaf775ef2 refactor(ynab): specify a budget when creating a connection 2024-05-07 17:23:16 +02:00
Rıdvan Akca
7b4179a87f refactor(google-tasks/find-task): filter all tasks when finding a task 2024-05-07 16:51:23 +02:00
Ali BARIN
43281bcbfe Merge pull request #1863 from automatisch/vtiger-fix
refactor(vtiger-crm): update field names and keys
2024-05-07 16:15:00 +02:00
Rıdvan Akca
8a35d47caf refactor(vtiger-crm): update field names and keys 2024-05-07 15:42:35 +02:00
Ali BARIN
759e8b6c42 fix(vtiger-crm/triggers): correct pagination 2024-05-07 11:33:55 +00:00
Ali BARIN
a8b01244af fix(vtiger-crm/create-case): mark contactName required 2024-05-07 11:33:37 +00:00
Ali BARIN
6ffb16ac67 fix(vtiger-crm): use DOCS_URL variable in authDocUrl 2024-05-07 11:33:17 +00:00
Ali BARIN
5b66cc6c8b Merge pull request #1527 from automatisch/AUT-521
feat(vtiger-crm): add create todo action
2024-05-07 13:23:49 +02:00
Rıdvan Akca
9a96258265 feat(vtiger-crm): add create case action 2024-05-07 11:22:51 +00:00
Rıdvan Akca
a6cc1566c7 feat(vtiger-crm): add create contact action 2024-05-07 11:22:51 +00:00
Rıdvan Akca
d8d6227125 feat(vtiger-crm): add create lead action 2024-05-07 11:22:51 +00:00
Rıdvan Akca
fbfa67e471 feat(vtiger-crm): add create todo action 2024-05-07 11:22:51 +00:00
Rıdvan Akca
ab897ada5a feat(vtiger-crm): add create opportunity action 2024-05-07 11:22:49 +00:00
Ali BARIN
3bcd3f3cb7 Merge pull request #1524 from automatisch/AUT-528
feat(vtiger-crm): add new todos trigger
2024-05-07 12:18:12 +02:00
Ali BARIN
acbede8631 Merge pull request #1523 from automatisch/AUT-530
feat(vtiger-crm): add new opportunities trigger
2024-05-07 12:16:40 +02:00
Ali BARIN
fa8c7571d7 Merge pull request #1522 from automatisch/AUT-531
feat(vtiger-crm): add new leads trigger
2024-05-07 12:14:52 +02:00
Ali BARIN
a58575c5a1 Merge pull request #1521 from automatisch/AUT-532
feat(vtiger-crm): add new invoices trigger
2024-05-07 12:13:44 +02:00
Ali BARIN
51f7009a80 Merge pull request #1520 from automatisch/AUT-533
feat(vtiger-crm): add new contacts trigger
2024-05-07 12:09:49 +02:00
Ali BARIN
ba24c77f06 Merge pull request #1519 from automatisch/AUT-534
feat(vtiger-crm): add new cases trigger
2024-05-07 11:41:57 +02:00
Rıdvan Akca
6b712c9a90 feat(disqus): add new flagged comments trigger 2024-05-07 10:43:47 +02:00
Ali BARIN
033b15a158 Merge pull request #1484 from automatisch/AUT-510
feat(google-tasks): add google tasks integration
2024-05-06 18:23:28 +02:00
Ali BARIN
e398bb84d4 fix(google-tasks): use DOCS_URL variable in auth doc url 2024-05-06 16:15:32 +00:00
Ali BARIN
05e902ab0c fix(google-tasks): enable triggers 2024-05-06 16:13:52 +00:00
Ali BARIN
36eee61cb5 fix(google-tasks/triggers): use id instead of etag 2024-05-06 16:13:30 +00:00
Ali BARIN
2409ce6fce fix(google-tasks/newCompletedTasks): use unique meaningful key 2024-05-06 16:13:12 +00:00
Ali BARIN
999d61e520 Merge pull request #1485 from automatisch/AUT-511
feat(google-tasks): add find task action
2024-05-06 18:12:02 +02:00
Ali BARIN
08d2418190 Merge pull request #1486 from automatisch/AUT-512
feat(google-tasks): add update task action
2024-05-06 18:08:25 +02:00
Ali BARIN
6c07faeaaf Merge pull request #1487 from automatisch/AUT-513
feat(google-tasks): add create task action
2024-05-06 18:05:49 +02:00
Ali BARIN
3f5cfbf5a2 Merge pull request #1488 from automatisch/AUT-514
feat(google-tasks): add create task list action
2024-05-06 18:02:40 +02:00
Ali BARIN
28f7707c75 Merge pull request #1489 from automatisch/AUT-515
feat(google-tasks): add new task lists trigger
2024-05-06 17:59:11 +02:00
Ali BARIN
dc8358f6ce Merge pull request #1490 from automatisch/AUT-516
feat(google-tasks): add new tasks trigger
2024-05-06 17:56:25 +02:00
Ali BARIN
ffcda04677 Merge pull request #1492 from automatisch/AUT-517
feat(google-tasks): add new completed tasks trigger
2024-05-06 17:50:23 +02:00
Ali BARIN
64ef655abb Merge pull request #1862 from automatisch/remove-dockerfile-cloud
chore(docker): remove Dockerfile.cloud
2024-05-06 17:00:01 +02:00
Ali BARIN
67887b1220 Merge pull request #1472 from automatisch/AUT-506
feat(disqus): add disqus integration
2024-05-06 16:14:31 +02:00
Ali BARIN
07803d1263 Merge pull request #1476 from automatisch/AUT-509
feat(disqus): add new comments trigger
2024-05-06 16:11:19 +02:00
Ali BARIN
f5ff7f7e13 chore(docker): remove Dockerfile.cloud 2024-05-06 12:42:05 +00:00
Ali BARIN
641d062b82 Merge pull request #1846 from automatisch/encode-uri-component
feat(formatter): add encode uri using encodeURIComponent action
2024-05-06 14:40:27 +02:00
Rıdvan Akca
bb28a06ee9 feat(formatter): add encode uri using encodeURIComponent action 2024-05-06 14:12:04 +02:00
Ali BARIN
81338c60f2 Merge pull request #1845 from automatisch/AUT-955
feat(formatter): add encode uri action
2024-05-06 13:44:40 +02:00
Ömer Faruk Aydın
f47fa5d272 Merge pull request #1859 from automatisch/remove-docker-cloud-entrypoint
chore(docker): remove cloud entrypoint
2024-05-03 15:16:20 +02:00
Ali BARIN
29e9e012a5 chore(docker): remove cloud entrypoint 2024-05-03 13:12:35 +00:00
Ömer Faruk Aydın
f89ddb5847 Merge pull request #1858 from automatisch/automatisch-version
fix: Do not use the version from npm packages
2024-05-03 15:11:51 +02:00
Faruk AYDIN
c721f063ef fix: Do not use the version from npm packages 2024-05-03 15:04:32 +02:00
Ömer Faruk Aydın
a709565336 Merge pull request #1857 from automatisch/latest-dockerfile
feat: Use latest docker image for docker compose
2024-05-03 14:53:25 +02:00
Faruk AYDIN
91e484aef1 feat: Use latest docker image for docker compose 2024-05-03 14:48:44 +02:00
Rıdvan Akca
6ba94dcc8e feat(formatter): add encode uri action 2024-04-27 13:21:50 +02:00
Rıdvan Akca
c413ab030b feat(airtable): add airtable integration 2024-01-19 17:20:56 +03:00
Rıdvan Akca
0f39007f92 feat(ynab): add new transactions trigger 2024-01-18 18:08:42 +03:00
Rıdvan Akca
ab811daba7 feat(ynab): add goal completed trigger 2024-01-18 18:00:48 +03:00
Rıdvan Akca
1428bf8ffa feat(ynab): add category overspent trigger 2024-01-18 17:31:53 +03:00
Rıdvan Akca
53f7f38e23 feat(ynab): add low account balance trigger 2024-01-18 16:00:52 +03:00
Rıdvan Akca
ce6ad9e0d3 feat(ynab): add you need a budget app integration 2024-01-18 15:54:20 +03:00
Rıdvan Akca
17a0c6123a feat(vtiger-crm): add new todos trigger 2024-01-17 10:47:52 +03:00
Rıdvan Akca
10edc5a8ad feat(vtiger-crm): add new opportunities trigger 2024-01-16 18:09:05 +03:00
Rıdvan Akca
bdf07d6bd5 feat(vtiger-crm): add new leads trigger 2024-01-16 18:05:13 +03:00
Rıdvan Akca
10ff65e71a feat(vtiger-crm): add new invoices trigger 2024-01-16 18:00:41 +03:00
Rıdvan Akca
9395097fda feat(vtiger-crm): add new contacts trigger 2024-01-16 17:55:48 +03:00
Rıdvan Akca
47eb0e00e3 feat(vtiger-crm): add new cases trigger 2024-01-16 17:48:24 +03:00
Rıdvan Akca
57d5f34ac5 feat(vtiger-crm): add vtiger crm integration 2024-01-16 17:42:34 +03:00
Rıdvan Akca
06b040412a feat(google-tasks): add new completed tasks trigger 2024-01-16 17:30:25 +03:00
Rıdvan Akca
cab040c74a feat(google-tasks): add new tasks trigger 2024-01-16 17:25:25 +03:00
Rıdvan Akca
21706a7d15 feat(google-tasks): add new task lists trigger 2024-01-16 17:17:24 +03:00
Rıdvan Akca
22e4b8aaeb feat(google-tasks): add create task list action 2024-01-16 17:10:27 +03:00
Rıdvan Akca
bd43a6021a feat(google-tasks): add create task action 2024-01-16 16:39:38 +03:00
Rıdvan Akca
a90b58b6db feat(google-tasks): add update task action 2024-01-16 16:33:41 +03:00
Rıdvan Akca
92a9b096ec feat(google-tasks): add find task action 2024-01-16 16:27:45 +03:00
Rıdvan Akca
7a6aa99840 feat(google-tasks): add google tasks integration 2024-01-16 13:55:03 +03:00
Rıdvan Akca
5657f0d793 feat(disqus): add new comments trigger 2024-01-16 13:40:00 +03:00
Rıdvan Akca
798529007e feat(disqus): add disqus integration 2024-01-16 13:23:26 +03:00
243 changed files with 10562 additions and 317 deletions

View File

@@ -36,7 +36,6 @@ services:
keycloak:
image: quay.io/keycloak/keycloak:21.1
restart: always
container_name: keycloak
environment:
- KEYCLOAK_ADMIN=admin
- KEYCLOAK_ADMIN_PASSWORD=admin

View File

@@ -1,24 +0,0 @@
# syntax=docker/dockerfile:1
FROM node:18-alpine
ENV PORT 3000
RUN \
apk --no-cache add --virtual build-dependencies python3 build-base git
RUN git clone https://github.com/automatisch/automatisch.git
WORKDIR /automatisch
RUN yarn install
RUN if [ "$WORKER" != "true" ]; then cd packages/web && yarn build; fi
RUN \
rm -rf /usr/local/share/.cache/ && \
apk del build-dependencies
COPY ./docker/entrypoint-cloud.sh /entrypoint-cloud.sh
EXPOSE 3000
ENTRYPOINT ["sh", "/entrypoint-cloud.sh"]

View File

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

View File

@@ -1,13 +0,0 @@
#!/bin/sh
set -e
cd packages/backend
if [ -n "$WORKER" ]; then
yarn start:worker
else
yarn db:migrate
yarn db:seed:user
yarn start
fi

View File

@@ -2,6 +2,7 @@ import appConfig from '../../src/config/app.js';
import logger from '../../src/helpers/logger.js';
import client from './client.js';
import User from '../../src/models/user.js';
import Config from '../../src/models/config.js';
import Role from '../../src/models/role.js';
import '../../src/config/orm.js';
import process from 'process';
@@ -21,6 +22,14 @@ export async function createUser(
email = 'user@automatisch.io',
password = 'sample'
) {
if (appConfig.disableSeedUser) {
logger.info('Seed user is disabled.');
process.exit(0);
return;
}
const UNIQUE_VIOLATION_CODE = '23505';
const role = await fetchAdminRole();
@@ -37,6 +46,8 @@ export async function createUser(
if (userCount === 0) {
const user = await User.query().insertAndFetch(userParams);
logger.info(`User has been saved: ${user.email}`);
await Config.markInstallationCompleted();
} else {
logger.info('No need to seed a user.');
}

View File

@@ -67,6 +67,7 @@
"pluralize": "^8.0.0",
"raw-body": "^2.5.2",
"showdown": "^2.1.0",
"uuid": "^9.0.1",
"winston": "^3.7.1",
"xmlrpc": "^1.3.2"
},

View File

@@ -0,0 +1,92 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Create record',
key: 'createRecord',
description: 'Creates a new record with fields that automatically populate.',
arguments: [
{
label: 'Base',
key: 'baseId',
type: 'dropdown',
required: true,
description: 'Base in which to create the record.',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listBases',
},
],
},
},
{
label: 'Table',
key: 'tableId',
type: 'dropdown',
required: true,
dependsOn: ['parameters.baseId'],
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listTables',
},
{
name: 'parameters.baseId',
value: '{parameters.baseId}',
},
],
},
additionalFields: {
type: 'query',
name: 'getDynamicFields',
arguments: [
{
name: 'key',
value: 'listFields',
},
{
name: 'parameters.baseId',
value: '{parameters.baseId}',
},
{
name: 'parameters.tableId',
value: '{parameters.tableId}',
},
],
},
},
],
async run($) {
const { baseId, tableId, ...rest } = $.step.parameters;
const fields = Object.entries(rest).reduce((result, [key, value]) => {
if (Array.isArray(value)) {
result[key] = value.map((item) => item.value);
} else if (value !== '') {
result[key] = value;
}
return result;
}, {});
const body = {
typecast: true,
fields,
};
const { data } = await $.http.post(`/v0/${baseId}/${tableId}`, body);
$.setActionItem({
raw: data,
});
},
});

View File

@@ -0,0 +1,174 @@
import defineAction from '../../../../helpers/define-action.js';
import { URLSearchParams } from 'url';
export default defineAction({
name: 'Find record',
key: 'findRecord',
description:
"Finds a record using simple field search or use Airtable's formula syntax to find a matching record.",
arguments: [
{
label: 'Base',
key: 'baseId',
type: 'dropdown',
required: true,
description: 'Base in which to create the record.',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listBases',
},
],
},
},
{
label: 'Table',
key: 'tableId',
type: 'dropdown',
required: true,
dependsOn: ['parameters.baseId'],
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listTables',
},
{
name: 'parameters.baseId',
value: '{parameters.baseId}',
},
],
},
},
{
label: 'Search by field',
key: 'tableField',
type: 'dropdown',
required: false,
dependsOn: ['parameters.baseId', 'parameters.tableId'],
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listTableFields',
},
{
name: 'parameters.baseId',
value: '{parameters.baseId}',
},
{
name: 'parameters.tableId',
value: '{parameters.tableId}',
},
],
},
},
{
label: 'Search Value',
key: 'searchValue',
type: 'string',
required: false,
variables: true,
description:
'The value of unique identifier for the record. For date values, please use the ISO format (e.g., "YYYY-MM-DD").',
},
{
label: 'Search for exact match?',
key: 'exactMatch',
type: 'dropdown',
required: true,
description: '',
variables: true,
options: [
{ label: 'Yes', value: 'true' },
{ label: 'No', value: 'false' },
],
},
{
label: 'Search Formula',
key: 'searchFormula',
type: 'string',
required: false,
variables: true,
description:
'Instead, you have the option to use an Airtable search formula for locating records according to sophisticated criteria and across various fields.',
},
{
label: 'Limit to View',
key: 'limitToView',
type: 'dropdown',
required: false,
dependsOn: ['parameters.baseId', 'parameters.tableId'],
description:
'You have the choice to restrict the search to a particular view ID if desired.',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listTableViews',
},
{
name: 'parameters.baseId',
value: '{parameters.baseId}',
},
{
name: 'parameters.tableId',
value: '{parameters.tableId}',
},
],
},
},
],
async run($) {
const {
baseId,
tableId,
tableField,
searchValue,
exactMatch,
searchFormula,
limitToView,
} = $.step.parameters;
let filterByFormula;
if (tableField && searchValue) {
filterByFormula =
exactMatch === 'true'
? `{${tableField}} = '${searchValue}'`
: `LOWER({${tableField}}) = LOWER('${searchValue}')`;
} else {
filterByFormula = searchFormula;
}
const body = new URLSearchParams({
filterByFormula,
view: limitToView,
});
const { data } = await $.http.post(
`/v0/${baseId}/${tableId}/listRecords`,
body
);
$.setActionItem({
raw: data,
});
},
});

View File

@@ -0,0 +1,4 @@
import createRecord from './create-record/index.js';
import findRecord from './find-record/index.js';
export default [createRecord, findRecord];

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="256px" height="215px" viewBox="0 0 256 215" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
<g>
<path d="M114.25873,2.70101695 L18.8604023,42.1756384 C13.5552723,44.3711638 13.6102328,51.9065311 18.9486282,54.0225085 L114.746142,92.0117514 C123.163769,95.3498757 132.537419,95.3498757 140.9536,92.0117514 L236.75256,54.0225085 C242.08951,51.9065311 242.145916,44.3711638 236.83934,42.1756384 L141.442459,2.70101695 C132.738459,-0.900338983 122.961284,-0.900338983 114.25873,2.70101695" fill="#FFBF00"></path>
<path d="M136.349071,112.756863 L136.349071,207.659101 C136.349071,212.173089 140.900664,215.263892 145.096461,213.600615 L251.844122,172.166219 C254.281184,171.200072 255.879376,168.845451 255.879376,166.224705 L255.879376,71.3224678 C255.879376,66.8084791 251.327783,63.7176768 247.131986,65.3809537 L140.384325,106.815349 C137.94871,107.781496 136.349071,110.136118 136.349071,112.756863" fill="#26B5F8"></path>
<path d="M111.422771,117.65355 L79.742409,132.949912 L76.5257763,134.504714 L9.65047684,166.548104 C5.4112904,168.593211 0.000578531073,165.503855 0.000578531073,160.794612 L0.000578531073,71.7210757 C0.000578531073,70.0173017 0.874160452,68.5463864 2.04568588,67.4384994 C2.53454463,66.9481944 3.08848814,66.5446689 3.66412655,66.2250305 C5.26231864,65.2661153 7.54173107,65.0101153 9.47981017,65.7766689 L110.890522,105.957098 C116.045234,108.002206 116.450206,115.225166 111.422771,117.65355" fill="#ED3049"></path>
<path d="M111.422771,117.65355 L79.742409,132.949912 L2.04568588,67.4384994 C2.53454463,66.9481944 3.08848814,66.5446689 3.66412655,66.2250305 C5.26231864,65.2661153 7.54173107,65.0101153 9.47981017,65.7766689 L110.890522,105.957098 C116.045234,108.002206 116.450206,115.225166 111.422771,117.65355" fill-opacity="0.25" fill="#000000"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,38 @@
import crypto from 'crypto';
import { URLSearchParams } from 'url';
import authScope from '../common/auth-scope.js';
export default async function generateAuthUrl($) {
const oauthRedirectUrlField = $.app.auth.fields.find(
(field) => field.key == 'oAuthRedirectUrl'
);
const redirectUri = oauthRedirectUrlField.value;
const state = crypto.randomBytes(100).toString('base64url');
const codeVerifier = crypto.randomBytes(96).toString('base64url');
const codeChallenge = crypto
.createHash('sha256')
.update(codeVerifier)
.digest('base64')
.replace(/=/g, '')
.replace(/\+/g, '-')
.replace(/\//g, '_');
const searchParams = new URLSearchParams({
client_id: $.auth.data.clientId,
redirect_uri: redirectUri,
response_type: 'code',
scope: authScope.join(' '),
state,
code_challenge: codeChallenge,
code_challenge_method: 'S256',
});
const url = `https://airtable.com/oauth2/v1/authorize?${searchParams.toString()}`;
await $.auth.set({
url,
originalCodeChallenge: codeChallenge,
originalState: state,
codeVerifier,
});
}

View File

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

View File

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

View File

@@ -0,0 +1,40 @@
import { URLSearchParams } from 'node:url';
import authScope from '../common/auth-scope.js';
const refreshToken = async ($) => {
const params = new URLSearchParams({
client_id: $.auth.data.clientId,
grant_type: 'refresh_token',
refresh_token: $.auth.data.refreshToken,
});
const basicAuthToken = Buffer.from(
$.auth.data.clientId + ':' + $.auth.data.clientSecret
).toString('base64');
const { data } = await $.http.post(
'https://airtable.com/oauth2/v1/token',
params.toString(),
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: `Basic ${basicAuthToken}`,
},
additionalProperties: {
skipAddingAuthHeader: true,
},
}
);
await $.auth.set({
accessToken: data.access_token,
refreshToken: data.refresh_token,
expiresIn: data.expires_in,
refreshExpiresIn: data.refresh_expires_in,
scope: authScope.join(' '),
tokenType: data.token_type,
});
};
export default refreshToken;

View File

@@ -0,0 +1,56 @@
import getCurrentUser from '../common/get-current-user.js';
const verifyCredentials = async ($) => {
if ($.auth.data.originalState !== $.auth.data.state) {
throw new Error("The 'state' parameter does not match.");
}
if ($.auth.data.originalCodeChallenge !== $.auth.data.code_challenge) {
throw new Error("The 'code challenge' parameter does not match.");
}
const oauthRedirectUrlField = $.app.auth.fields.find(
(field) => field.key == 'oAuthRedirectUrl'
);
const redirectUri = oauthRedirectUrlField.value;
const basicAuthToken = Buffer.from(
$.auth.data.clientId + ':' + $.auth.data.clientSecret
).toString('base64');
const { data } = await $.http.post(
'https://airtable.com/oauth2/v1/token',
{
code: $.auth.data.code,
client_id: $.auth.data.clientId,
redirect_uri: redirectUri,
grant_type: 'authorization_code',
code_verifier: $.auth.data.codeVerifier,
},
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: `Basic ${basicAuthToken}`,
},
additionalProperties: {
skipAddingAuthHeader: true,
},
}
);
await $.auth.set({
accessToken: data.access_token,
tokenType: data.token_type,
});
const currentUser = await getCurrentUser($);
await $.auth.set({
clientId: $.auth.data.clientId,
clientSecret: $.auth.data.clientSecret,
scope: $.auth.data.scope,
expiresIn: data.expires_in,
refreshExpiresIn: data.refresh_expires_in,
refreshToken: data.refresh_token,
screenName: currentUser.email,
});
};
export default verifyCredentials;

View File

@@ -0,0 +1,12 @@
const addAuthHeader = ($, requestConfig) => {
if (
!requestConfig.additionalProperties?.skipAddingAuthHeader &&
$.auth.data?.accessToken
) {
requestConfig.headers.Authorization = `${$.auth.data.tokenType} ${$.auth.data.accessToken}`;
}
return requestConfig;
};
export default addAuthHeader;

View File

@@ -0,0 +1,12 @@
const authScope = [
'data.records:read',
'data.records:write',
'data.recordComments:read',
'data.recordComments:write',
'schema.bases:read',
'schema.bases:write',
'user.email:read',
'webhook:manage',
];
export default authScope;

View File

@@ -0,0 +1,6 @@
const getCurrentUser = async ($) => {
const { data: currentUser } = await $.http.get('/v0/meta/whoami');
return currentUser;
};
export default getCurrentUser;

View File

@@ -0,0 +1,6 @@
import listBases from './list-bases/index.js';
import listTableFields from './list-table-fields/index.js';
import listTableViews from './list-table-views/index.js';
import listTables from './list-tables/index.js';
export default [listBases, listTableFields, listTableViews, listTables];

View File

@@ -0,0 +1,28 @@
export default {
name: 'List bases',
key: 'listBases',
async run($) {
const bases = {
data: [],
};
const params = {};
do {
const { data } = await $.http.get('/v0/meta/bases', { params });
params.offset = data.offset;
if (data?.bases) {
for (const base of data.bases) {
bases.data.push({
value: base.id,
name: base.name,
});
}
}
} while (params.offset);
return bases;
},
};

View File

@@ -0,0 +1,39 @@
export default {
name: 'List table fields',
key: 'listTableFields',
async run($) {
const tableFields = {
data: [],
};
const { baseId, tableId } = $.step.parameters;
if (!baseId) {
return tableFields;
}
const params = {};
do {
const { data } = await $.http.get(`/v0/meta/bases/${baseId}/tables`, {
params,
});
params.offset = data.offset;
if (data?.tables) {
for (const table of data.tables) {
if (table.id === tableId) {
table.fields.forEach((field) => {
tableFields.data.push({
value: field.name,
name: field.name,
});
});
}
}
}
} while (params.offset);
return tableFields;
},
};

View File

@@ -0,0 +1,39 @@
export default {
name: 'List table views',
key: 'listTableViews',
async run($) {
const tableViews = {
data: [],
};
const { baseId, tableId } = $.step.parameters;
if (!baseId) {
return tableViews;
}
const params = {};
do {
const { data } = await $.http.get(`/v0/meta/bases/${baseId}/tables`, {
params,
});
params.offset = data.offset;
if (data?.tables) {
for (const table of data.tables) {
if (table.id === tableId) {
table.views.forEach((view) => {
tableViews.data.push({
value: view.id,
name: view.name,
});
});
}
}
}
} while (params.offset);
return tableViews;
},
};

View File

@@ -0,0 +1,35 @@
export default {
name: 'List tables',
key: 'listTables',
async run($) {
const tables = {
data: [],
};
const baseId = $.step.parameters.baseId;
if (!baseId) {
return tables;
}
const params = {};
do {
const { data } = await $.http.get(`/v0/meta/bases/${baseId}/tables`, {
params,
});
params.offset = data.offset;
if (data?.tables) {
for (const table of data.tables) {
tables.data.push({
value: table.id,
name: table.name,
});
}
}
} while (params.offset);
return tables;
},
};

View File

@@ -0,0 +1,3 @@
import listFields from './list-fields/index.js';
export default [listFields];

View File

@@ -0,0 +1,86 @@
const hasValue = (value) => value !== null && value !== undefined;
export default {
name: 'List fields',
key: 'listFields',
async run($) {
const options = [];
const { baseId, tableId } = $.step.parameters;
if (!hasValue(baseId) || !hasValue(tableId)) {
return;
}
const { data } = await $.http.get(`/v0/meta/bases/${baseId}/tables`);
const selectedTable = data.tables.find((table) => table.id === tableId);
if (!selectedTable) return;
selectedTable.fields.forEach((field) => {
if (field.type === 'singleSelect') {
options.push({
label: field.name,
key: field.name,
type: 'dropdown',
required: false,
variables: true,
options: field.options.choices.map((choice) => ({
label: choice.name,
value: choice.id,
})),
});
} else if (field.type === 'multipleSelects') {
options.push({
label: field.name,
key: field.name,
type: 'dynamic',
required: false,
variables: true,
fields: [
{
label: 'Value',
key: 'value',
type: 'dropdown',
required: false,
variables: true,
options: field.options.choices.map((choice) => ({
label: choice.name,
value: choice.id,
})),
},
],
});
} else if (field.type === 'checkbox') {
options.push({
label: field.name,
key: field.name,
type: 'dropdown',
required: false,
variables: true,
options: [
{
label: 'Yes',
value: 'true',
},
{
label: 'No',
value: 'false',
},
],
});
} else {
options.push({
label: field.name,
key: field.name,
type: 'string',
required: false,
variables: true,
});
}
});
return options;
},
};

View File

@@ -0,0 +1,22 @@
import defineApp from '../../helpers/define-app.js';
import addAuthHeader from './common/add-auth-header.js';
import auth from './auth/index.js';
import actions from './actions/index.js';
import dynamicData from './dynamic-data/index.js';
import dynamicFields from './dynamic-fields/index.js';
export default defineApp({
name: 'Airtable',
key: 'airtable',
baseUrl: 'https://airtable.com',
apiBaseUrl: 'https://api.airtable.com',
iconUrl: '{BASE_URL}/apps/airtable/assets/favicon.svg',
authDocUrl: '{DOCS_URL}/apps/airtable/connection',
primaryColor: 'FFBF00',
supportsConnections: true,
beforeRequest: [addAuthHeader],
auth,
actions,
dynamicData,
dynamicFields,
});

View File

@@ -0,0 +1 @@
<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" width="132" height="24" fill="none" viewBox="0 0 132 24"><path fill="#19191C" d="M38.557 19.495c2.16 0 3.25-1.113 3.725-1.87h.214c.094.805.664 1.562 1.779 1.562h2.111V16.82h-.545c-.38 0-.57-.213-.57-.545V6.776h-2.8v1.516h-.213c-.545-.758-1.684-1.824-3.772-1.824-3.321 0-5.789 2.748-5.789 6.514s2.515 6.513 5.86 6.513m.498-2.7c-1.969 0-3.51-1.445-3.51-3.79 0-2.297 1.494-3.86 3.487-3.86 1.898 0 3.487 1.397 3.487 3.86 0 2.108-1.352 3.79-3.463 3.79M48.04 24h2.799v-6.376h.213c.522.758 1.637 1.871 3.844 1.871 3.321 0 5.741-2.795 5.741-6.513 0-3.743-2.586-6.514-5.931-6.514-2.135 0-3.18 1.16-3.678 1.8h-.213V6.776h-2.776V24m6.263-7.134c-1.922 0-3.512-1.42-3.512-3.884 0-2.108 1.353-3.885 3.464-3.885 1.97 0 3.511 1.54 3.511 3.885 0 2.297-1.494 3.884-3.463 3.884M62.082 24h2.8v-6.376h.213c.522.758 1.637 1.871 3.843 1.871 3.321 0 5.51-2.795 5.51-6.513 0-3.743-2.355-6.514-5.7-6.514-2.135 0-3.179 1.16-3.677 1.8h-.214V6.776h-2.775zm6.263-7.134c-1.922 0-3.511-1.42-3.511-3.884 0-2.108 1.352-3.885 3.463-3.885 1.97 0 3.512 1.54 3.512 3.885 0 2.297-1.495 3.884-3.464 3.884m9.805 2.61h3.961l2.254-9.735h.143l2.253 9.735H90.7l3.153-12.412h-2.821l-2.254 9.759h-.214l-2.253-9.759h-3.725l-2.278 9.759h-.213l-2.23-9.759h-2.99l3.274 12.412m17.123 0h2.8V13.34c0-2.345 1.09-3.79 3.131-3.79h1.233V6.756h-.925c-1.59 0-2.8 1.09-3.274 2.132h-.19V7.064h-2.775zm21.057 0h2.183v-2.487h-2.159c-.854 0-1.21-.38-1.21-1.256V9.528h3.511V7.064h-3.511V3.582h-2.657v3.482h-2.325v2.464h2.159v6.229c0 2.63 1.589 3.719 4.009 3.719m9.693.019c2.586 0 4.864-1.279 5.67-3.86l-2.562-.616c-.451 1.373-1.755 2.084-3.131 2.084-2.041 0-3.393-1.326-3.417-3.41h9.419v-.782c0-3.695-2.301-6.443-6.097-6.443-3.346 0-6.216 2.63-6.216 6.537 0 3.79 2.538 6.49 6.334 6.49m-3.416-7.84c.166-1.492 1.518-2.747 3.298-2.747 1.708 0 3.108 1.066 3.25 2.747h-6.548"/><path fill="#19191C" fill-rule="evenodd" d="M108.916 19.476h-2.8V9.528h-2.182V7.064h4.982z" clip-rule="evenodd"/><path fill="#19191C" d="M107.309 5.342c1.02 0 1.779-.758 1.779-1.753 0-.971-.759-1.73-1.779-1.73-1.021 0-1.78.759-1.78 1.73 0 .995.759 1.753 1.78 1.753"/><path fill="#FD366E" d="M24.443 16.432v5.478H10.752c-3.989 0-7.472-2.203-9.335-5.478A11.041 11.041 0 0 1 0 11.695v-1.48a10.97 10.97 0 0 1 .381-2.247C1.661 3.368 5.82 0 10.751 0c4.934 0 9.092 3.37 10.371 7.967h-5.854c-.96-1.499-2.624-2.49-4.516-2.49s-3.555.991-4.516 2.49a5.47 5.47 0 0 0-.67 1.494 5.562 5.562 0 0 0-.202 1.494 5.5 5.5 0 0 0 1.69 3.983 5.32 5.32 0 0 0 3.698 1.494h13.69"/><path fill="#FD366E" d="M24.443 9.46v5.478h-9.994a5.5 5.5 0 0 0 1.691-3.983 5.56 5.56 0 0 0-.203-1.494h8.506"/></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,65 @@
import verifyCredentials from './verify-credentials.js';
import isStillVerified from './is-still-verified.js';
export default {
fields: [
{
key: 'screenName',
label: 'Screen Name',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description:
'Screen name of your connection to be used on Automatisch UI.',
clickToCopy: false,
},
{
key: 'projectId',
label: 'Project ID',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description: 'Project ID of your Appwrite project.',
clickToCopy: false,
},
{
key: 'apiKey',
label: 'API Key',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description: 'API key of your Appwrite project.',
clickToCopy: false,
},
{
key: 'instanceUrl',
label: 'Appwrite instance URL',
type: 'string',
required: false,
readOnly: false,
placeholder: '',
description: '',
clickToCopy: true,
},
{
key: 'host',
label: 'Host Name',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description: 'Host name of your Appwrite project.',
clickToCopy: false,
},
],
verifyCredentials,
isStillVerified,
};

View File

@@ -0,0 +1,5 @@
const verifyCredentials = async ($) => {
await $.http.get('/v1/users');
};
export default verifyCredentials;

View File

@@ -0,0 +1,16 @@
const addAuthHeader = ($, requestConfig) => {
requestConfig.headers['Content-Type'] = 'application/json';
if ($.auth.data?.apiKey && $.auth.data?.projectId) {
requestConfig.headers['X-Appwrite-Project'] = $.auth.data.projectId;
requestConfig.headers['X-Appwrite-Key'] = $.auth.data.apiKey;
}
if ($.auth.data?.host) {
requestConfig.headers['Host'] = $.auth.data.host;
}
return requestConfig;
};
export default addAuthHeader;

View File

@@ -0,0 +1,13 @@
const setBaseUrl = ($, requestConfig) => {
const instanceUrl = $.auth.data.instanceUrl;
if (instanceUrl) {
requestConfig.baseURL = instanceUrl;
} else if ($.app.apiBaseUrl) {
requestConfig.baseURL = $.app.apiBaseUrl;
}
return requestConfig;
};
export default setBaseUrl;

View File

@@ -0,0 +1,4 @@
import listCollections from './list-collections/index.js';
import listDatabases from './list-databases/index.js';
export default [listCollections, listDatabases];

View File

@@ -0,0 +1,44 @@
export default {
name: 'List collections',
key: 'listCollections',
async run($) {
const collections = {
data: [],
};
const databaseId = $.step.parameters.databaseId;
if (!databaseId) {
return collections;
}
const params = {
queries: [
JSON.stringify({
method: 'orderAsc',
attribute: 'name',
}),
JSON.stringify({
method: 'limit',
values: [100],
}),
],
};
const { data } = await $.http.get(
`/v1/databases/${databaseId}/collections`,
{ params }
);
if (data?.collections) {
for (const collection of data.collections) {
collections.data.push({
value: collection.$id,
name: collection.name,
});
}
}
return collections;
},
};

View File

@@ -0,0 +1,36 @@
export default {
name: 'List databases',
key: 'listDatabases',
async run($) {
const databases = {
data: [],
};
const params = {
queries: [
JSON.stringify({
method: 'orderAsc',
attribute: 'name',
}),
JSON.stringify({
method: 'limit',
values: [100],
}),
],
};
const { data } = await $.http.get('/v1/databases', { params });
if (data?.databases) {
for (const database of data.databases) {
databases.data.push({
value: database.$id,
name: database.name,
});
}
}
return databases;
},
};

View File

@@ -0,0 +1,21 @@
import defineApp from '../../helpers/define-app.js';
import addAuthHeader from './common/add-auth-header.js';
import setBaseUrl from './common/set-base-url.js';
import auth from './auth/index.js';
import triggers from './triggers/index.js';
import dynamicData from './dynamic-data/index.js';
export default defineApp({
name: 'Appwrite',
key: 'appwrite',
baseUrl: 'https://appwrite.io',
apiBaseUrl: 'https://cloud.appwrite.io',
iconUrl: '{BASE_URL}/apps/appwrite/assets/favicon.svg',
authDocUrl: '{DOCS_URL}/apps/appwrite/connection',
primaryColor: 'FD366E',
supportsConnections: true,
beforeRequest: [setBaseUrl, addAuthHeader],
auth,
triggers,
dynamicData,
});

View File

@@ -0,0 +1,3 @@
import newDocuments from './new-documents/index.js';
export default [newDocuments];

View File

@@ -0,0 +1,104 @@
import defineTrigger from '../../../../helpers/define-trigger.js';
export default defineTrigger({
name: 'New documents',
key: 'newDocuments',
pollInterval: 15,
description: 'Triggers when a new document is created.',
arguments: [
{
label: 'Database',
key: 'databaseId',
type: 'dropdown',
required: true,
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listDatabases',
},
],
},
},
{
label: 'Collection',
key: 'collectionId',
type: 'dropdown',
required: true,
dependsOn: ['parameters.databaseId'],
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listCollections',
},
{
name: 'parameters.databaseId',
value: '{parameters.databaseId}',
},
],
},
},
],
async run($) {
const { databaseId, collectionId } = $.step.parameters;
const limit = 1;
let lastDocumentId = undefined;
let offset = 0;
let documentCount = 0;
do {
const params = {
queries: [
JSON.stringify({
method: 'orderDesc',
attribute: '$createdAt',
}),
JSON.stringify({
method: 'limit',
values: [limit],
}),
// An invalid cursor shouldn't be sent.
lastDocumentId &&
JSON.stringify({
method: 'cursorAfter',
values: [lastDocumentId],
}),
].filter(Boolean),
};
const { data } = await $.http.get(
`/v1/databases/${databaseId}/collections/${collectionId}/documents`,
{ params }
);
const documents = data?.documents;
documentCount = documents?.length;
offset = offset + limit;
lastDocumentId = documents[documentCount - 1]?.$id;
if (!documentCount) {
return;
}
for (const document of documents) {
$.pushTriggerItem({
raw: document,
meta: {
internalId: document.$id,
},
});
}
} while (documentCount === limit);
},
});

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="200px" height="200px" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
<g id="background">
<rect fill="#2E9FFF" width="200" height="200"/>
</g>
<g id="Layer_2">
</g>
<path fill="#FFFFFF" d="M102.535,167.5c-16.518,0-31.621-6.036-43.298-16.021L30.5,155.405l11.102-27.401
c-3.868-8.535-6.038-18.01-6.038-28.004c0-37.277,29.984-67.5,66.971-67.5c36.984,0,66.965,30.223,66.965,67.5
C169.5,137.284,139.52,167.5,102.535,167.5z M139.102,99.807v-0.188c0-19.479-13.736-33.367-37.42-33.367h-25.58v67.5h25.201
C125.171,133.753,139.102,119.284,139.102,99.807L139.102,99.807z M101.964,117.168h-7.482V82.841h7.482
c10.989,0,18.283,6.265,18.283,17.07v0.188C120.247,110.995,112.953,117.168,101.964,117.168z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,21 @@
import { URLSearchParams } from 'url';
import authScope from '../common/auth-scope.js';
export default async function generateAuthUrl($) {
const oauthRedirectUrlField = $.app.auth.fields.find(
(field) => field.key == 'oAuthRedirectUrl'
);
const redirectUri = oauthRedirectUrlField.value;
const searchParams = new URLSearchParams({
client_id: $.auth.data.apiKey,
scope: authScope.join(','),
response_type: 'code',
redirect_uri: redirectUri,
});
const url = `https://disqus.com/api/oauth/2.0/authorize/?${searchParams.toString()}`;
await $.auth.set({
url,
});
}

View File

@@ -0,0 +1,48 @@
import generateAuthUrl from './generate-auth-url.js';
import verifyCredentials from './verify-credentials.js';
import refreshToken from './refresh-token.js';
import isStillVerified from './is-still-verified.js';
export default {
fields: [
{
key: 'oAuthRedirectUrl',
label: 'OAuth Redirect URL',
type: 'string',
required: true,
readOnly: true,
value: '{WEB_APP_URL}/app/disqus/connections/add',
placeholder: null,
description:
'When asked to input a redirect URL in Disqus, enter the URL above.',
clickToCopy: true,
},
{
key: 'apiKey',
label: 'API Key',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
clickToCopy: false,
},
{
key: 'apiSecret',
label: 'API Secret',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
clickToCopy: false,
},
],
generateAuthUrl,
verifyCredentials,
isStillVerified,
refreshToken,
};

View File

@@ -0,0 +1,8 @@
import getCurrentUser from '../common/get-current-user.js';
const isStillVerified = async ($) => {
const currentUser = await getCurrentUser($);
return !!currentUser.response.username;
};
export default isStillVerified;

View File

@@ -0,0 +1,26 @@
import { URLSearchParams } from 'node:url';
import authScope from '../common/auth-scope.js';
const refreshToken = async ($) => {
const params = new URLSearchParams({
grant_type: 'refresh_token',
client_id: $.auth.data.apiKey,
client_secret: $.auth.data.apiSecret,
refresh_token: $.auth.data.refreshToken,
});
const { data } = await $.http.post(
`https://disqus.com/api/oauth/2.0/access_token/`,
params.toString()
);
await $.auth.set({
accessToken: data.access_token,
refreshToken: data.refresh_token,
expiresIn: data.expires_in,
scope: authScope.join(','),
tokenType: data.token_type,
});
};
export default refreshToken;

View File

@@ -0,0 +1,34 @@
import { URLSearchParams } from 'url';
const verifyCredentials = async ($) => {
const oauthRedirectUrlField = $.app.auth.fields.find(
(field) => field.key == 'oAuthRedirectUrl'
);
const redirectUri = oauthRedirectUrlField.value;
const params = new URLSearchParams({
grant_type: 'authorization_code',
client_id: $.auth.data.apiKey,
client_secret: $.auth.data.apiSecret,
redirect_uri: redirectUri,
code: $.auth.data.code,
});
const { data } = await $.http.post(
`https://disqus.com/api/oauth/2.0/access_token/`,
params.toString()
);
await $.auth.set({
accessToken: data.access_token,
tokenType: data.token_type,
apiKey: $.auth.data.apiKey,
apiSecret: $.auth.data.apiSecret,
scope: $.auth.data.scope,
userId: data.user_id,
expiresIn: data.expires_in,
refreshToken: data.refresh_token,
screenName: data.username,
});
};
export default verifyCredentials;

View File

@@ -0,0 +1,15 @@
import { URLSearchParams } from 'url';
const addAuthHeader = ($, requestConfig) => {
const params = new URLSearchParams({
access_token: $.auth.data.accessToken,
api_key: $.auth.data.apiKey,
api_secret: $.auth.data.apiSecret,
});
requestConfig.params = params;
return requestConfig;
};
export default addAuthHeader;

View File

@@ -0,0 +1,3 @@
const authScope = ['read', 'write', 'admin', 'email'];
export default authScope;

View File

@@ -0,0 +1,10 @@
const getCurrentUser = async ($) => {
try {
const { data: currentUser } = await $.http.get('/3.0/users/details.json');
return currentUser;
} catch (error) {
throw new Error('You are not authenticated.');
}
};
export default getCurrentUser;

View File

@@ -0,0 +1,3 @@
import listForums from './list-forums/index.js';
export default [listForums];

View File

@@ -0,0 +1,36 @@
export default {
name: 'List forums',
key: 'listForums',
async run($) {
const forums = {
data: [],
};
const params = {
limit: 100,
order: 'desc',
cursor: undefined,
};
let more;
do {
const { data } = await $.http.get('/3.0/users/listForums.json', {
params,
});
params.cursor = data.cursor.next;
more = data.cursor.hasNext;
if (data.response?.length) {
for (const forum of data.response) {
forums.data.push({
value: forum.id,
name: forum.id,
});
}
}
} while (more);
return forums;
},
};

View File

@@ -0,0 +1,20 @@
import defineApp from '../../helpers/define-app.js';
import addAuthHeader from './common/add-auth-header.js';
import auth from './auth/index.js';
import dynamicData from './dynamic-data/index.js';
import triggers from './triggers/index.js';
export default defineApp({
name: 'Disqus',
key: 'disqus',
baseUrl: 'https://disqus.com',
apiBaseUrl: 'https://disqus.com/api',
iconUrl: '{BASE_URL}/apps/disqus/assets/favicon.svg',
authDocUrl: '{DOCS_URL}/apps/disqus/connection',
primaryColor: '2E9FFF',
supportsConnections: true,
beforeRequest: [addAuthHeader],
auth,
dynamicData,
triggers,
});

View File

@@ -0,0 +1,4 @@
import newComments from './new-comments/index.js';
import newFlaggedComments from './new-flagged-comments/index.js';
export default [newComments, newFlaggedComments];

View File

@@ -0,0 +1,92 @@
import defineTrigger from '../../../../helpers/define-trigger.js';
import { URLSearchParams } from 'url';
export default defineTrigger({
name: 'New comments',
key: 'newComments',
pollInterval: 15,
description: 'Triggers when a new comment is posted in a forum using Disqus.',
arguments: [
{
label: 'Post Types',
key: 'postTypes',
type: 'dynamic',
required: false,
description:
'Which posts should be considered for inclusion in the trigger?',
fields: [
{
label: 'Type',
key: 'type',
type: 'dropdown',
required: false,
description: '',
variables: true,
options: [
{ label: 'Unapproved Posts', value: 'unapproved' },
{ label: 'Approved Posts', value: 'approved' },
{ label: 'Spam Posts', value: 'spam' },
{ label: 'Deleted Posts', value: 'deleted' },
{ label: 'Flagged Posts', value: 'flagged' },
{ label: 'Highlighted Posts', value: 'highlighted' },
],
},
],
},
{
label: 'Forum',
key: 'forumId',
type: 'dropdown',
required: true,
description: 'Select the forum where you want comments to be triggered.',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listForums',
},
],
},
},
],
async run($) {
const forumId = $.step.parameters.forumId;
const postTypes = $.step.parameters.postTypes;
const formattedCommentTypes = postTypes
.filter((type) => type.type !== '')
.map((type) => type.type);
const params = new URLSearchParams({
limit: '100',
forum: forumId,
});
if (formattedCommentTypes.length) {
formattedCommentTypes.forEach((type) => params.append('include', type));
}
let more;
do {
const { data } = await $.http.get(
`/3.0/posts/list.json?${params.toString()}`
);
params.set('cursor', data.cursor.next);
more = data.cursor.hasNext;
if (data.response?.length) {
for (const comment of data.response) {
$.pushTriggerItem({
raw: comment,
meta: {
internalId: comment.id,
},
});
}
}
} while (more);
},
});

View File

@@ -0,0 +1,60 @@
import defineTrigger from '../../../../helpers/define-trigger.js';
import { URLSearchParams } from 'url';
export default defineTrigger({
name: 'New flagged comments',
key: 'newFlaggedComments',
pollInterval: 15,
description: 'Triggers when a Disqus comment is marked with a flag',
arguments: [
{
label: 'Forum',
key: 'forumId',
type: 'dropdown',
required: true,
description: 'Select the forum where you want comments to be triggered.',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listForums',
},
],
},
},
],
async run($) {
const forumId = $.step.parameters.forumId;
const isFlaggedFilter = 5;
const params = new URLSearchParams({
limit: 100,
forum: forumId,
filters: [isFlaggedFilter],
});
let more;
do {
const { data } = await $.http.get(
`/3.0/posts/list.json?${params.toString()}`
);
params.set('cursor', data.cursor.next);
more = data.cursor.hasNext;
if (data.response?.length) {
for (const comment of data.response) {
$.pushTriggerItem({
raw: comment,
meta: {
internalId: comment.id,
},
});
}
}
} while (more);
},
});

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="256px" height="256px" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
<g>
<circle fill="#F05537" cx="128" cy="128" r="128"></circle>
<path d="M117.475323,82.7290398 C136.772428,78.4407943 156.069532,86.3025777 166.790146,101.311437 L81.5017079,120.608542 C84.3605382,102.26438 98.1782181,87.0172853 117.475323,82.7290398 Z M167.266618,153.48509 C160.596014,163.252761 150.351872,170.161601 138.678314,172.782195 C119.38121,177.070441 99.8458692,169.208657 89.1252554,153.961562 L174.651929,134.664457 L188.469609,131.567391 L215.152026,125.611495 C214.91379,119.893834 214.199082,114.176173 213.007903,108.696749 C202.287289,62.7172275 155.354825,33.8906884 108.42236,44.6113021 C61.4898956,55.3319159 32.1868848,101.073201 43.1457344,147.290958 C54.1045839,193.508715 100.798813,222.097018 147.731277,211.376404 C175.366637,205.182272 196.807864,186.599875 207.766714,163.014525 L167.266618,153.48509 L167.266618,153.48509 Z" fill="#FFFFFF" fill-rule="nonzero"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,19 @@
import { URLSearchParams } from 'url';
export default async function generateAuthUrl($) {
const oauthRedirectUrlField = $.app.auth.fields.find(
(field) => field.key == 'oAuthRedirectUrl'
);
const redirectUri = oauthRedirectUrlField.value;
const searchParams = new URLSearchParams({
response_type: 'code',
client_id: $.auth.data.clientId,
redirect_uri: redirectUri,
});
const url = `https://www.eventbrite.com/oauth/authorize?${searchParams.toString()}`;
await $.auth.set({
url,
});
}

View File

@@ -0,0 +1,46 @@
import generateAuthUrl from './generate-auth-url.js';
import verifyCredentials from './verify-credentials.js';
import isStillVerified from './is-still-verified.js';
export default {
fields: [
{
key: 'oAuthRedirectUrl',
label: 'OAuth Redirect URL',
type: 'string',
required: true,
readOnly: true,
value: '{WEB_APP_URL}/app/eventbrite/connections/add',
placeholder: null,
description:
'When asked to input a redirect URL in Eventbrite, enter the URL above.',
clickToCopy: true,
},
{
key: 'clientId',
label: 'API Key',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
clickToCopy: false,
},
{
key: 'clientSecret',
label: 'Client Secret',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
clickToCopy: false,
},
],
generateAuthUrl,
verifyCredentials,
isStillVerified,
};

View File

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

View File

@@ -0,0 +1,42 @@
import getCurrentUser from '../common/get-current-user.js';
const verifyCredentials = async ($) => {
const oauthRedirectUrlField = $.app.auth.fields.find(
(field) => field.key == 'oAuthRedirectUrl'
);
const redirectUri = oauthRedirectUrlField.value;
const { data } = await $.http.post(
'https://www.eventbrite.com/oauth/token',
{
grant_type: 'authorization_code',
client_id: $.auth.data.clientId,
client_secret: $.auth.data.clientSecret,
code: $.auth.data.code,
redirect_uri: redirectUri,
},
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
}
);
await $.auth.set({
accessToken: data.access_token,
tokenType: data.token_type,
});
const currentUser = await getCurrentUser($);
const screenName = [currentUser.name, currentUser.emails[0].email]
.filter(Boolean)
.join(' @ ');
await $.auth.set({
clientId: $.auth.data.clientId,
clientSecret: $.auth.data.clientSecret,
screenName,
});
};
export default verifyCredentials;

View File

@@ -0,0 +1,9 @@
const addAuthHeader = ($, requestConfig) => {
if ($.auth.data?.accessToken) {
requestConfig.headers.Authorization = `Bearer ${$.auth.data.accessToken}`;
}
return requestConfig;
};
export default addAuthHeader;

View File

@@ -0,0 +1,6 @@
const getCurrentUser = async ($) => {
const { data: currentUser } = await $.http.get('/v3/users/me');
return currentUser;
};
export default getCurrentUser;

View File

@@ -0,0 +1,4 @@
import listEvents from './list-events/index.js';
import listOrganizations from './list-organizations/index.js';
export default [listEvents, listOrganizations];

View File

@@ -0,0 +1,44 @@
export default {
name: 'List events',
key: 'listEvents',
async run($) {
const events = {
data: [],
};
const organizationId = $.step.parameters.organizationId;
if (!organizationId) {
return events;
}
const params = {
continuation: undefined,
order_by: 'created_desc',
};
do {
const { data } = await $.http.get(
`/v3/organizations/${organizationId}/events/`,
{
params,
}
);
if (data.pagination.has_more_items) {
params.continuation = data.pagination.continuation;
}
if (data.events) {
for (const event of data.events) {
events.data.push({
value: event.id,
name: `${event.name.text} (${event.status})`,
});
}
}
} while (params.continuation);
return events;
},
};

View File

@@ -0,0 +1,35 @@
export default {
name: 'List organizations',
key: 'listOrganizations',
async run($) {
const organizations = {
data: [],
};
const params = {
continuation: undefined,
};
do {
const { data } = await $.http.get('/v3/users/me/organizations', {
params,
});
if (data.pagination.has_more_items) {
params.continuation = data.pagination.continuation;
}
if (data.organizations) {
for (const organization of data.organizations) {
organizations.data.push({
value: organization.id,
name: organization.name,
});
}
}
} while (params.continuation);
return organizations;
},
};

View File

@@ -0,0 +1,20 @@
import defineApp from '../../helpers/define-app.js';
import addAuthHeader from './common/add-auth-header.js';
import auth from './auth/index.js';
import dynamicData from './dynamic-data/index.js';
import triggers from './triggers/index.js';
export default defineApp({
name: 'Eventbrite',
key: 'eventbrite',
baseUrl: 'https://www.eventbrite.com',
apiBaseUrl: 'https://www.eventbriteapi.com',
iconUrl: '{BASE_URL}/apps/eventbrite/assets/favicon.svg',
authDocUrl: '{DOCS_URL}/apps/eventbrite/connection',
primaryColor: 'F05537',
supportsConnections: true,
beforeRequest: [addAuthHeader],
auth,
dynamicData,
triggers,
});

View File

@@ -0,0 +1,11 @@
import newAttendeeCheckIn from './new-attendee-check-in/index.js';
import newAttendeeRegistered from './new-attendee-registered/index.js';
import newEvents from './new-events/index.js';
import updatedAttendee from './updated-attendee/index.js';
export default [
updatedAttendee,
newAttendeeCheckIn,
newAttendeeRegistered,
newEvents,
];

View File

@@ -0,0 +1,120 @@
import Crypto from 'crypto';
import defineTrigger from '../../../../helpers/define-trigger.js';
export default defineTrigger({
name: 'New attendee check in',
key: 'newAttendeeCheckIn',
type: 'webhook',
description: "Triggers when an attendee's barcode is scanned in.",
arguments: [
{
label: 'Organization',
key: 'organizationId',
type: 'dropdown',
required: true,
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listOrganizations',
},
],
},
},
{
label: 'Event',
key: 'eventId',
type: 'dropdown',
required: false,
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listEvents',
},
{
name: 'parameters.organizationId',
value: '{parameters.organizationId}',
},
],
},
},
],
async run($) {
const dataItem = {
raw: $.request.body,
meta: {
internalId: Crypto.randomUUID(),
},
};
$.pushTriggerItem(dataItem);
},
async testRun($) {
const eventId = $.step.parameters.eventId;
const organizationId = $.step.parameters.organizationId;
const params = {
event_id: eventId,
};
const {
data: { orders },
} = await $.http.get(`/v3/events/${eventId}/orders/`, params);
if (orders.length === 0) {
return;
}
const computedWebhookEvent = {
config: {
action: 'barcode.checked_in',
user_id: organizationId,
webhook_id: '11111111',
endpoint_url: $.webhookUrl,
},
api_url: orders[0].resource_uri,
};
const dataItem = {
raw: computedWebhookEvent,
meta: {
internalId: computedWebhookEvent.user_id,
},
};
$.pushTriggerItem(dataItem);
},
async registerHook($) {
const organizationId = $.step.parameters.organizationId;
const eventId = $.step.parameters.eventId;
const payload = {
endpoint_url: $.webhookUrl,
actions: 'attendee.checked_in',
event_id: eventId,
};
const { data } = await $.http.post(
`/v3/organizations/${organizationId}/webhooks/`,
payload
);
await $.flow.setRemoteWebhookId(data.id);
},
async unregisterHook($) {
await $.http.delete(`/v3/webhooks/${$.flow.remoteWebhookId}/`);
},
});

View File

@@ -0,0 +1,120 @@
import Crypto from 'crypto';
import defineTrigger from '../../../../helpers/define-trigger.js';
export default defineTrigger({
name: 'New attendee registered',
key: 'newAttendeeRegistered',
type: 'webhook',
description: 'Triggers when an attendee orders a ticket for an event.',
arguments: [
{
label: 'Organization',
key: 'organizationId',
type: 'dropdown',
required: true,
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listOrganizations',
},
],
},
},
{
label: 'Event',
key: 'eventId',
type: 'dropdown',
required: false,
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listEvents',
},
{
name: 'parameters.organizationId',
value: '{parameters.organizationId}',
},
],
},
},
],
async run($) {
const dataItem = {
raw: $.request.body,
meta: {
internalId: Crypto.randomUUID(),
},
};
$.pushTriggerItem(dataItem);
},
async testRun($) {
const eventId = $.step.parameters.eventId;
const organizationId = $.step.parameters.organizationId;
const params = {
event_id: eventId,
};
const {
data: { orders },
} = await $.http.get(`/v3/events/${eventId}/orders/`, params);
if (orders.length === 0) {
return;
}
const computedWebhookEvent = {
config: {
action: 'order.placed',
user_id: organizationId,
webhook_id: '11111111',
endpoint_url: $.webhookUrl,
},
api_url: orders[0].resource_uri,
};
const dataItem = {
raw: computedWebhookEvent,
meta: {
internalId: computedWebhookEvent.user_id,
},
};
$.pushTriggerItem(dataItem);
},
async registerHook($) {
const organizationId = $.step.parameters.organizationId;
const eventId = $.step.parameters.eventId;
const payload = {
endpoint_url: $.webhookUrl,
actions: 'order.placed',
event_id: eventId,
};
const { data } = await $.http.post(
`/v3/organizations/${organizationId}/webhooks/`,
payload
);
await $.flow.setRemoteWebhookId(data.id);
},
async unregisterHook($) {
await $.http.delete(`/v3/webhooks/${$.flow.remoteWebhookId}/`);
},
});

View File

@@ -0,0 +1,98 @@
import Crypto from 'crypto';
import defineTrigger from '../../../../helpers/define-trigger.js';
export default defineTrigger({
name: 'New events',
key: 'newEvents',
type: 'webhook',
description:
'Triggers when a new event is published and live within an organization.',
arguments: [
{
label: 'Organization',
key: 'organizationId',
type: 'dropdown',
required: true,
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listOrganizations',
},
],
},
},
],
async run($) {
const dataItem = {
raw: $.request.body,
meta: {
internalId: Crypto.randomUUID(),
},
};
$.pushTriggerItem(dataItem);
},
async testRun($) {
const organizationId = $.step.parameters.organizationId;
const params = {
orderBy: 'created_desc',
status: 'all',
};
const {
data: { events },
} = await $.http.get(`/v3/organizations/${organizationId}/events/`, params);
if (events.length === 0) {
return;
}
const computedWebhookEvent = {
config: {
action: 'event.published',
user_id: events[0].organization_id,
webhook_id: '11111111',
endpoint_url: $.webhookUrl,
},
api_url: events[0].resource_uri,
};
const dataItem = {
raw: computedWebhookEvent,
meta: {
internalId: computedWebhookEvent.user_id,
},
};
$.pushTriggerItem(dataItem);
},
async registerHook($) {
const organizationId = $.step.parameters.organizationId;
const payload = {
endpoint_url: $.webhookUrl,
actions: 'event.published',
event_id: '',
};
const { data } = await $.http.post(
`/v3/organizations/${organizationId}/webhooks/`,
payload
);
await $.flow.setRemoteWebhookId(data.id);
},
async unregisterHook($) {
await $.http.delete(`/v3/webhooks/${$.flow.remoteWebhookId}/`);
},
});

View File

@@ -0,0 +1,120 @@
import Crypto from 'crypto';
import defineTrigger from '../../../../helpers/define-trigger.js';
export default defineTrigger({
name: 'Updated Attendee',
key: 'updatedAttendee',
type: 'webhook',
description: 'Triggers when attendee data is updated.',
arguments: [
{
label: 'Organization',
key: 'organizationId',
type: 'dropdown',
required: true,
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listOrganizations',
},
],
},
},
{
label: 'Event',
key: 'eventId',
type: 'dropdown',
required: false,
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listEvents',
},
{
name: 'parameters.organizationId',
value: '{parameters.organizationId}',
},
],
},
},
],
async run($) {
const dataItem = {
raw: $.request.body,
meta: {
internalId: Crypto.randomUUID(),
},
};
$.pushTriggerItem(dataItem);
},
async testRun($) {
const eventId = $.step.parameters.eventId;
const organizationId = $.step.parameters.organizationId;
const params = {
event_id: eventId,
};
const {
data: { attendees },
} = await $.http.get(`/v3/events/${eventId}/attendees/`, params);
if (attendees.length === 0) {
return;
}
const computedWebhookEvent = {
config: {
action: 'attendee.updated',
user_id: organizationId,
webhook_id: '11111111',
endpoint_url: $.webhookUrl,
},
api_url: attendees[0].resource_uri,
};
const dataItem = {
raw: computedWebhookEvent,
meta: {
internalId: computedWebhookEvent.user_id,
},
};
$.pushTriggerItem(dataItem);
},
async registerHook($) {
const organizationId = $.step.parameters.organizationId;
const eventId = $.step.parameters.eventId;
const payload = {
endpoint_url: $.webhookUrl,
actions: 'attendee.updated',
event_id: eventId,
};
const { data } = await $.http.post(
`/v3/organizations/${organizationId}/webhooks/`,
payload
);
await $.flow.setRemoteWebhookId(data.id);
},
async unregisterHook($) {
await $.http.delete(`/v3/webhooks/${$.flow.remoteWebhookId}/`);
},
});

View File

@@ -5,11 +5,24 @@ const formatDateTime = ($) => {
const fromFormat = $.step.parameters.fromFormat;
const fromTimezone = $.step.parameters.fromTimezone;
let inputDateTime;
const inputDateTime = DateTime.fromFormat(input, fromFormat, {
zone: fromTimezone,
setZone: true,
});
if (fromFormat === 'X') {
inputDateTime = DateTime.fromSeconds(Number(input), fromFormat, {
zone: fromTimezone,
setZone: true,
});
} else if (fromFormat === 'x') {
inputDateTime = DateTime.fromMillis(Number(input), fromFormat, {
zone: fromTimezone,
setZone: true,
});
} else {
inputDateTime = DateTime.fromFormat(input, fromFormat, {
zone: fromTimezone,
setZone: true,
});
}
const toFormat = $.step.parameters.toFormat;
const toTimezone = $.step.parameters.toTimezone;

View File

@@ -2,6 +2,7 @@ import defineAction from '../../../../helpers/define-action.js';
import base64ToString from './transformers/base64-to-string.js';
import capitalize from './transformers/capitalize.js';
import encodeUriComponent from './transformers/encode-uri-component.js';
import extractEmailAddress from './transformers/extract-email-address.js';
import extractNumber from './transformers/extract-number.js';
import htmlToMarkdown from './transformers/html-to-markdown.js';
@@ -10,12 +11,14 @@ import markdownToHtml from './transformers/markdown-to-html.js';
import pluralize from './transformers/pluralize.js';
import replace from './transformers/replace.js';
import stringToBase64 from './transformers/string-to-base64.js';
import encodeUri from './transformers/encode-uri.js';
import trimWhitespace from './transformers/trim-whitespace.js';
import useDefaultValue from './transformers/use-default-value.js';
const transformers = {
base64ToString,
capitalize,
encodeUriComponent,
extractEmailAddress,
extractNumber,
htmlToMarkdown,
@@ -24,6 +27,7 @@ const transformers = {
pluralize,
replace,
stringToBase64,
encodeUri,
trimWhitespace,
useDefaultValue,
};
@@ -43,6 +47,10 @@ export default defineAction({
options: [
{ label: 'Base64 to String', value: 'base64ToString' },
{ label: 'Capitalize', value: 'capitalize' },
{
label: 'Encode URI Component',
value: 'encodeUriComponent',
},
{ label: 'Convert HTML to Markdown', value: 'htmlToMarkdown' },
{ label: 'Convert Markdown to HTML', value: 'markdownToHtml' },
{ label: 'Extract Email Address', value: 'extractEmailAddress' },
@@ -51,6 +59,7 @@ export default defineAction({
{ label: 'Pluralize', value: 'pluralize' },
{ label: 'Replace', value: 'replace' },
{ label: 'String to Base64', value: 'stringToBase64' },
{ label: 'Encode URI', value: 'encodeUri' },
{ label: 'Trim Whitespace', value: 'trimWhitespace' },
{ label: 'Use Default Value', value: 'useDefaultValue' },
],

View File

@@ -0,0 +1,8 @@
const encodeUriComponent = ($) => {
const input = $.step.parameters.input;
const encodedString = encodeURIComponent(input);
return encodedString;
};
export default encodeUriComponent;

View File

@@ -0,0 +1,8 @@
const encodeUri = ($) => {
const input = $.step.parameters.input;
const encodedString = encodeURI(input);
return encodedString;
};
export default encodeUri;

View File

@@ -1,5 +1,6 @@
import base64ToString from './text/base64-to-string.js';
import capitalize from './text/capitalize.js';
import encodeUriComponent from './text/encode-uri-component.js';
import extractEmailAddress from './text/extract-email-address.js';
import extractNumber from './text/extract-number.js';
import htmlToMarkdown from './text/html-to-markdown.js';
@@ -8,6 +9,7 @@ import markdownToHtml from './text/markdown-to-html.js';
import pluralize from './text/pluralize.js';
import replace from './text/replace.js';
import stringToBase64 from './text/string-to-base64.js';
import encodeUri from './text/encode-uri.js';
import trimWhitespace from './text/trim-whitespace.js';
import useDefaultValue from './text/use-default-value.js';
import performMathOperation from './numbers/perform-math-operation.js';
@@ -19,6 +21,7 @@ import formatDateTime from './date-time/format-date-time.js';
const options = {
base64ToString,
capitalize,
encodeUriComponent,
extractEmailAddress,
extractNumber,
htmlToMarkdown,
@@ -27,6 +30,7 @@ const options = {
pluralize,
replace,
stringToBase64,
encodeUri,
trimWhitespace,
useDefaultValue,
performMathOperation,

View File

@@ -0,0 +1,12 @@
const encodeUriComponent = [
{
label: 'Input',
key: 'input',
type: 'string',
required: true,
description: 'URI Component to encode',
variables: true,
},
];
export default encodeUriComponent;

View File

@@ -0,0 +1,12 @@
const encodeUri = [
{
label: 'Input',
key: 'input',
type: 'string',
required: true,
description: 'URI to encode',
variables: true,
},
];
export default encodeUri;

View File

@@ -0,0 +1,31 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Create task list',
key: 'createTaskList',
description: 'Creates a new task list.',
arguments: [
{
label: 'List Title',
key: 'listTitle',
type: 'string',
required: true,
description: '',
variables: true,
},
],
async run($) {
const listTitle = $.step.parameters.listTitle;
const body = {
title: listTitle,
};
const { data } = await $.http.post('/tasks/v1/users/@me/lists', body);
$.setActionItem({
raw: data,
});
},
});

View File

@@ -0,0 +1,70 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Create task',
key: 'createTask',
description: 'Creates a new task.',
arguments: [
{
label: 'Task List',
key: 'taskListId',
type: 'dropdown',
required: true,
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listTaskLists',
},
],
},
},
{
label: 'Title',
key: 'title',
type: 'string',
required: true,
description: '',
variables: true,
},
{
label: 'Notes',
key: 'notes',
type: 'string',
required: false,
description: '',
variables: true,
},
{
label: 'Due Date',
key: 'due',
type: 'string',
required: false,
description: 'RFC 3339 timestamp.',
variables: true,
},
],
async run($) {
const { taskListId, title, notes, due } = $.step.parameters;
const body = {
title,
notes,
due,
};
const { data } = await $.http.post(
`/tasks/v1/lists/${taskListId}/tasks`,
body
);
$.setActionItem({
raw: data,
});
},
});

View File

@@ -0,0 +1,57 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Find task',
key: 'findTask',
description: 'Looking for a specific task.',
arguments: [
{
label: 'Task List',
key: 'taskListId',
type: 'dropdown',
required: true,
description: 'The list to be searched.',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listTaskLists',
},
],
},
},
{
label: 'Title',
key: 'title',
type: 'string',
required: true,
description: '',
variables: true,
},
],
async run($) {
const taskListId = $.step.parameters.taskListId;
const title = $.step.parameters.title;
const params = {
showCompleted: true,
showHidden: true,
};
const { data } = await $.http.get(`/tasks/v1/lists/${taskListId}/tasks`, {
params,
});
const filteredTask = data.items?.filter((task) =>
task.title.includes(title)
);
$.setActionItem({
raw: filteredTask[0],
});
},
});

View File

@@ -0,0 +1,6 @@
import createTask from './create-task/index.js';
import createTaskList from './create-task-list/index.js';
import findTask from './find-task/index.js';
import updateTask from './update-task/index.js';
export default [createTask, createTaskList, findTask, updateTask];

View File

@@ -0,0 +1,108 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Update task',
key: 'updateTask',
description: 'Updates an existing task.',
arguments: [
{
label: 'Task List',
key: 'taskListId',
type: 'dropdown',
required: true,
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listTaskLists',
},
],
},
},
{
label: 'Task',
key: 'taskId',
type: 'dropdown',
required: true,
description: 'Ensure that you choose a list before proceeding.',
variables: true,
dependsOn: ['parameters.taskListId'],
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listTasks',
},
{
name: 'parameters.taskListId',
value: '{parameters.taskListId}',
},
],
},
},
{
label: 'Title',
key: 'title',
type: 'string',
required: false,
description: 'Provide a new title for the revised task.',
variables: true,
},
{
label: 'Status',
key: 'status',
type: 'dropdown',
required: false,
description:
'Specify the status of the updated task. If you opt for a custom value, enter either "needsAttention" or "completed."',
variables: true,
options: [
{ label: 'Incomplete', value: 'needsAction' },
{ label: 'Complete', value: 'completed' },
],
},
{
label: 'Notes',
key: 'notes',
type: 'string',
required: false,
description: 'Provide a note for the revised task.',
variables: true,
},
{
label: 'Due Date',
key: 'due',
type: 'string',
required: false,
description:
'Specify the deadline for the task (as a RFC 3339 timestamp).',
variables: true,
},
],
async run($) {
const { taskListId, taskId, title, status, notes, due } = $.step.parameters;
const body = {
title,
status,
notes,
due,
};
const { data } = await $.http.patch(
`/tasks/v1/lists/${taskListId}/tasks/${taskId}`,
body
);
$.setActionItem({
raw: data,
});
},
});

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1"
id="svg8849" inkscape:version="1.1 (c68e22c387, 2021-05-23)" sodipodi:docname="google tasks.svg" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="527.1px" height="500px"
viewBox="0 0 527.1 500" enable-background="new 0 0 527.1 500" xml:space="preserve">
<sodipodi:namedview bordercolor="#eeeeee" borderopacity="1" id="namedview8851" inkscape:bbox-nodes="true" inkscape:bbox-paths="true" inkscape:current-layer="layer1" inkscape:cx="280.62992" inkscape:cy="277.14384" inkscape:document-units="mm" inkscape:object-paths="true" inkscape:pagecheckerboard="0" inkscape:pageopacity="0" inkscape:pageshadow="0" inkscape:snap-bbox="true" inkscape:snap-bbox-edge-midpoints="true" inkscape:snap-bbox-midpoints="true" inkscape:snap-center="true" inkscape:snap-global="true" inkscape:snap-intersection-paths="true" inkscape:snap-midpoints="true" inkscape:snap-object-midpoints="true" inkscape:snap-page="true" inkscape:snap-smooth-nodes="true" inkscape:snap-text-baseline="true" inkscape:window-height="1009" inkscape:window-maximized="1" inkscape:window-width="1920" inkscape:window-x="1912" inkscape:window-y="760" inkscape:zoom="1.4342733" pagecolor="#505050" showgrid="false" units="px">
</sodipodi:namedview>
<g>
<polygon fill="#0066DA" points="410.4,58.3 368.8,81.2 348.2,120.6 368.8,168.8 407.8,211 450,187.5 475.9,142.8 450,87.5 "/>
<path fill="#2684FC" d="M249.3,219.4l98.9-98.9c29.1,22.1,50.5,53.8,59.6,90.4L272.1,346.7c-12.2,12.2-32,12.2-44.2,0l-91.5-91.5
c-9.8-9.8-9.8-25.6,0-35.3l39-39c9.8-9.8,25.6-9.8,35.3,0L249.3,219.4z M519.8,63.6l-39.7-39.7c-9.7-9.7-25.6-9.7-35.3,0
l-34.4,34.4c27.5,23,49.9,51.8,65.5,84.5l43.9-43.9C529.6,89.2,529.6,73.3,519.8,63.6z M412.5,250c0,89.8-72.8,162.5-162.5,162.5
S87.5,339.8,87.5,250S160.2,87.5,250,87.5c36.9,0,70.9,12.3,98.2,33.1l62.2-62.2C367,21.9,311.1,0,250,0C111.9,0,0,111.9,0,250
s111.9,250,250,250s250-111.9,250-250c0-38.3-8.7-74.7-24.1-107.2L407.8,211C410.8,223.5,412.5,236.6,412.5,250z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,9 @@
const addAuthHeader = ($, requestConfig) => {
if ($.auth.data?.accessToken) {
requestConfig.headers.Authorization = `${$.auth.data.tokenType} ${$.auth.data.accessToken}`;
}
return requestConfig;
};
export default addAuthHeader;

View File

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

View File

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

View File

@@ -0,0 +1,4 @@
import listTaskLists from './list-task-lists/index.js';
import listTasks from './list-tasks/index.js';
export default [listTaskLists, listTasks];

View File

@@ -0,0 +1,33 @@
export default {
name: 'List task lists',
key: 'listTaskLists',
async run($) {
const taskLists = {
data: [],
};
const params = {
maxResults: 100,
pageToken: undefined,
};
do {
const { data } = await $.http.get('/tasks/v1/users/@me/lists', {
params,
});
params.pageToken = data.nextPageToken;
if (data.items) {
for (const taskList of data.items) {
taskLists.data.push({
value: taskList.id,
name: taskList.title,
});
}
}
} while (params.pageToken);
return taskLists;
},
};

View File

@@ -0,0 +1,40 @@
export default {
name: 'List tasks',
key: 'listTasks',
async run($) {
const tasks = {
data: [],
};
const taskListId = $.step.parameters.taskListId;
const params = {
maxResults: 100,
pageToken: undefined,
};
if (!taskListId) {
return tasks;
}
do {
const { data } = await $.http.get(`/tasks/v1/lists/${taskListId}/tasks`, {
params,
});
params.pageToken = data.nextPageToken;
if (data.items) {
for (const task of data.items) {
if (task.title !== '') {
tasks.data.push({
value: task.id,
name: task.title,
});
}
}
}
} while (params.pageToken);
return tasks;
},
};

View File

@@ -0,0 +1,22 @@
import defineApp from '../../helpers/define-app.js';
import addAuthHeader from './common/add-auth-header.js';
import auth from './auth/index.js';
import actions from './actions/index.js';
import triggers from './triggers/index.js';
import dynamicData from './dynamic-data/index.js';
export default defineApp({
name: 'Google Tasks',
key: 'google-tasks',
baseUrl: 'https://calendar.google.com/calendar/u/0/r/tasks',
apiBaseUrl: 'https://tasks.googleapis.com',
iconUrl: '{BASE_URL}/apps/google-tasks/assets/favicon.svg',
authDocUrl: '{DOCS_URL}/apps/google-tasks/connection',
primaryColor: '0066DA',
supportsConnections: true,
beforeRequest: [addAuthHeader],
auth,
actions,
dynamicData,
triggers,
});

View File

@@ -0,0 +1,5 @@
import newCompletedTasks from './new-completed-tasks/index.js';
import newTaskLists from './new-task-lists/index.js';
import newTasks from './new-tasks/index.js';
export default [newCompletedTasks, newTaskLists, newTasks];

View File

@@ -0,0 +1,59 @@
import defineTrigger from '../../../../helpers/define-trigger.js';
export default defineTrigger({
name: 'New completed tasks',
key: 'newCompletedTasks',
pollInterval: 15,
description: 'Triggers when a task is finished within a specified task list.',
arguments: [
{
label: 'Task List',
key: 'taskListId',
type: 'dropdown',
required: true,
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listTaskLists',
},
],
},
},
],
async run($) {
const taskListId = $.step.parameters.taskListId;
const params = {
maxResults: 100,
showCompleted: true,
showHidden: true,
pageToken: undefined,
};
do {
const { data } = await $.http.get(`/tasks/v1/lists/${taskListId}/tasks`, {
params,
});
params.pageToken = data.nextPageToken;
if (data.items?.length) {
for (const task of data.items) {
if (task.status === 'completed') {
$.pushTriggerItem({
raw: task,
meta: {
internalId: task.id,
},
});
}
}
}
} while (params.pageToken);
},
});

View File

@@ -0,0 +1,31 @@
import defineTrigger from '../../../../helpers/define-trigger.js';
export default defineTrigger({
name: 'New task lists',
key: 'newTaskLists',
pollInterval: 15,
description: 'Triggers when a new task list is created.',
async run($) {
const params = {
maxResults: 100,
pageToken: undefined,
};
do {
const { data } = await $.http.get('/tasks/v1/users/@me/lists');
params.pageToken = data.nextPageToken;
if (data.items?.length) {
for (const taskList of data.items.reverse()) {
$.pushTriggerItem({
raw: taskList,
meta: {
internalId: taskList.id,
},
});
}
}
} while (params.pageToken);
},
});

View File

@@ -0,0 +1,53 @@
import defineTrigger from '../../../../helpers/define-trigger.js';
export default defineTrigger({
name: 'New tasks',
key: 'newTasks',
pollInterval: 15,
description: 'Triggers when a new task is created.',
arguments: [
{
label: 'Task List',
key: 'taskListId',
type: 'dropdown',
required: true,
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listTaskLists',
},
],
},
},
],
async run($) {
const taskListId = $.step.parameters.taskListId;
const params = {
maxResults: 100,
pageToken: undefined,
};
do {
const { data } = await $.http.get(`/tasks/v1/lists/${taskListId}/tasks`);
params.pageToken = data.nextPageToken;
if (data.items?.length) {
for (const task of data.items) {
$.pushTriggerItem({
raw: task,
meta: {
internalId: task.id,
},
});
}
}
} while (params.pageToken);
},
});

View File

@@ -1,70 +0,0 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Create board',
key: 'createBoard',
description: 'Creates a new board.',
arguments: [
{
label: 'Board Name',
key: 'boardName',
type: 'string',
required: true,
description: 'Title for the board.',
variables: true,
},
{
label: 'Board Kind',
key: 'boardKind',
type: 'dropdown',
required: true,
description: '',
variables: true,
options: [
{
label: 'Main',
value: 'public',
},
{
label: 'Private',
value: 'private',
},
{
label: 'Shareable',
value: 'share',
},
],
},
{
label: 'Template ID',
key: 'templateId',
type: 'string',
required: false,
description:
"When you switch on developer mode, you'll spot the template IDs in your template store. Additionally, you have the option to utilize the Board ID from any board you've saved as a template.",
variables: true,
},
],
async run($) {
const { boardName, boardKind, templateId } = $.step.parameters;
const body = {
query: `mutation {
create_board (board_name: "${boardName}", board_kind: ${boardKind}${
templateId ? `, template_id: ${templateId}` : ''
}) {
id
name
board_kind
}
}`,
};
const { data } = await $.http.post('/', body);
$.setActionItem({
raw: data,
});
},
});

View File

@@ -1,3 +0,0 @@
import createBoard from './create-board/index.js';
export default [createBoard];

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