diff --git a/packages/backend/src/apps/appwrite/assets/favicon.svg b/packages/backend/src/apps/appwrite/assets/favicon.svg
new file mode 100644
index 00000000..63bf0f23
--- /dev/null
+++ b/packages/backend/src/apps/appwrite/assets/favicon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/backend/src/apps/appwrite/auth/index.js b/packages/backend/src/apps/appwrite/auth/index.js
new file mode 100644
index 00000000..dfdd374b
--- /dev/null
+++ b/packages/backend/src/apps/appwrite/auth/index.js
@@ -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,
+};
diff --git a/packages/backend/src/apps/appwrite/auth/is-still-verified.js b/packages/backend/src/apps/appwrite/auth/is-still-verified.js
new file mode 100644
index 00000000..6663679a
--- /dev/null
+++ b/packages/backend/src/apps/appwrite/auth/is-still-verified.js
@@ -0,0 +1,8 @@
+import verifyCredentials from './verify-credentials.js';
+
+const isStillVerified = async ($) => {
+ await verifyCredentials($);
+ return true;
+};
+
+export default isStillVerified;
diff --git a/packages/backend/src/apps/appwrite/auth/verify-credentials.js b/packages/backend/src/apps/appwrite/auth/verify-credentials.js
new file mode 100644
index 00000000..3cd61698
--- /dev/null
+++ b/packages/backend/src/apps/appwrite/auth/verify-credentials.js
@@ -0,0 +1,5 @@
+const verifyCredentials = async ($) => {
+ await $.http.get('/v1/users');
+};
+
+export default verifyCredentials;
diff --git a/packages/backend/src/apps/appwrite/common/add-auth-header.js b/packages/backend/src/apps/appwrite/common/add-auth-header.js
new file mode 100644
index 00000000..1bec6104
--- /dev/null
+++ b/packages/backend/src/apps/appwrite/common/add-auth-header.js
@@ -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;
diff --git a/packages/backend/src/apps/appwrite/common/set-base-url.js b/packages/backend/src/apps/appwrite/common/set-base-url.js
new file mode 100644
index 00000000..35a7a957
--- /dev/null
+++ b/packages/backend/src/apps/appwrite/common/set-base-url.js
@@ -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;
diff --git a/packages/backend/src/apps/appwrite/dynamic-data/index.js b/packages/backend/src/apps/appwrite/dynamic-data/index.js
new file mode 100644
index 00000000..45eecdb0
--- /dev/null
+++ b/packages/backend/src/apps/appwrite/dynamic-data/index.js
@@ -0,0 +1,4 @@
+import listCollections from './list-collections/index.js';
+import listDatabases from './list-databases/index.js';
+
+export default [listCollections, listDatabases];
diff --git a/packages/backend/src/apps/appwrite/dynamic-data/list-collections/index.js b/packages/backend/src/apps/appwrite/dynamic-data/list-collections/index.js
new file mode 100644
index 00000000..aa4b1a17
--- /dev/null
+++ b/packages/backend/src/apps/appwrite/dynamic-data/list-collections/index.js
@@ -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',
+ atttribute: '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;
+ },
+};
diff --git a/packages/backend/src/apps/appwrite/dynamic-data/list-databases/index.js b/packages/backend/src/apps/appwrite/dynamic-data/list-databases/index.js
new file mode 100644
index 00000000..5a815412
--- /dev/null
+++ b/packages/backend/src/apps/appwrite/dynamic-data/list-databases/index.js
@@ -0,0 +1,36 @@
+export default {
+ name: 'List databases',
+ key: 'listDatabases',
+
+ async run($) {
+ const databases = {
+ data: [],
+ };
+
+ const params = {
+ queries: [
+ JSON.stringify({
+ method: 'orderAsc',
+ atttribute: '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;
+ },
+};
diff --git a/packages/backend/src/apps/appwrite/index.js b/packages/backend/src/apps/appwrite/index.js
new file mode 100644
index 00000000..735c2491
--- /dev/null
+++ b/packages/backend/src/apps/appwrite/index.js
@@ -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,
+});
diff --git a/packages/backend/src/apps/appwrite/triggers/index.js b/packages/backend/src/apps/appwrite/triggers/index.js
new file mode 100644
index 00000000..30d4b6cc
--- /dev/null
+++ b/packages/backend/src/apps/appwrite/triggers/index.js
@@ -0,0 +1,3 @@
+import newDocuments from './new-documents/index.js';
+
+export default [newDocuments];
diff --git a/packages/backend/src/apps/appwrite/triggers/new-documents/index.js b/packages/backend/src/apps/appwrite/triggers/new-documents/index.js
new file mode 100644
index 00000000..6c5a7e5f
--- /dev/null
+++ b/packages/backend/src/apps/appwrite/triggers/new-documents/index.js
@@ -0,0 +1,103 @@
+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',
+ atttribute: '$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);
+ },
+});
diff --git a/packages/docs/pages/.vitepress/config.js b/packages/docs/pages/.vitepress/config.js
index 462fec41..219a1d51 100644
--- a/packages/docs/pages/.vitepress/config.js
+++ b/packages/docs/pages/.vitepress/config.js
@@ -41,6 +41,15 @@ export default defineConfig({
{ text: 'Connection', link: '/apps/airtable/connection' },
],
},
+ {
+ text: 'Appwrite',
+ collapsible: true,
+ collapsed: true,
+ items: [
+ { text: 'Triggers', link: '/apps/appwrite/triggers' },
+ { text: 'Connection', link: '/apps/appwrite/connection' },
+ ],
+ },
{
text: 'Carbone',
collapsible: true,
diff --git a/packages/docs/pages/apps/appwrite/connection.md b/packages/docs/pages/apps/appwrite/connection.md
new file mode 100644
index 00000000..1a0d91c4
--- /dev/null
+++ b/packages/docs/pages/apps/appwrite/connection.md
@@ -0,0 +1,20 @@
+# Appwrite
+
+:::info
+This page explains the steps you need to follow to set up the Appwrite
+connection in Automatisch. If any of the steps are outdated, please let us know!
+:::
+
+1. Login to your Appwrite account: [https://appwrite.io/](https://appwrite.io/).
+2. Go to your project's **Settings**.
+3. In the Settings, click on the **View API Keys** button in **API credentials** section.
+4. Click on the **Create API Key** button.
+5. Fill the name field and select **Never** for the expiration date.
+6. Click on the **Next** button.
+7. Click on the **Select all** and then click on the **Create** button.
+8. Now, copy your **API key secret** and paste the key into the **API Key** field in Automatisch.
+9. Write any screen name to be displayed in Automatisch.
+10. You can find your project ID next to your project name. Paste the id into **Project ID** field in Automatsich.
+11. If you are using self-hosted Appwrite project, you can paste the instace url into **Appwrite instance URL** field in Automatisch.
+12. Fill the host name field with the hostname of your instance URL. It's either `cloud.appwrite.io` or hostname of your instance URL.
+13. Start using Appwrite integration with Automatisch!
diff --git a/packages/docs/pages/apps/appwrite/triggers.md b/packages/docs/pages/apps/appwrite/triggers.md
new file mode 100644
index 00000000..2177a8a9
--- /dev/null
+++ b/packages/docs/pages/apps/appwrite/triggers.md
@@ -0,0 +1,12 @@
+---
+favicon: /favicons/appwrite.svg
+items:
+ - name: New documets
+ desc: Triggers when a new document is created.
+---
+
+
+
+
diff --git a/packages/docs/pages/guide/available-apps.md b/packages/docs/pages/guide/available-apps.md
index 40d2ad18..ad2b4c0c 100644
--- a/packages/docs/pages/guide/available-apps.md
+++ b/packages/docs/pages/guide/available-apps.md
@@ -3,6 +3,7 @@
The following integrations are currently supported by Automatisch.
- [Airtable](/apps/airtable/actions)
+- [Appwrite](/apps/appwrite/triggers)
- [Carbone](/apps/carbone/actions)
- [Datastore](/apps/datastore/actions)
- [DeepL](/apps/deepl/actions)
diff --git a/packages/docs/pages/public/favicons/appwrite.svg b/packages/docs/pages/public/favicons/appwrite.svg
new file mode 100644
index 00000000..63bf0f23
--- /dev/null
+++ b/packages/docs/pages/public/favicons/appwrite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file