diff --git a/docs/docs/data-sources/appwrite.md b/docs/docs/data-sources/appwrite.md
new file mode 100644
index 0000000000..a4b9dd701b
--- /dev/null
+++ b/docs/docs/data-sources/appwrite.md
@@ -0,0 +1,40 @@
+---
+sidebar_position: 4
+---
+
+# Appwrite Database
+
+## Supported operations
+1. List documents
+2. Get document
+3. Create document
+4. Update document
+5. Delete document
+6. Bulk update using document id
+
+## Connection
+ToolJet connects to your Appwrite app using :
+- Host (API endpoint)
+- Project ID
+- Secret key
+
+To generate a new secret key and get another credentials, go to your perticular project settings page
+
+You should also set scopes for access perticular resources.
+[Read More about API keys and scopes](https://appwrite.io/docs/keys).
+
+Once the credentails are available, click on `+` button of data sources panel at the left-bottom corner of the app editor. Select Appwrite from the modal that pops up. Provide credentails. Click on 'Test connection' button to verify if the service account can access Appwrite from ToolJet server. Click on 'Save' button to save the datasource.
+
+
+
+## Querying Appwrite
+
+Click on `+` button of the query manager at the bottom panel of the editor and select the database added in the previous step as the data source.
+
+
+
+Select the operation that you want to perform on Appwrite database and click 'Save' to save the query.
+
+:::tip
+Query results can be transformed using transformations. Read our transformations documentation to see how: [link](/docs/tutorial/transformations)
+:::
\ No newline at end of file
diff --git a/docs/static/img/datasource-reference/appwrite/appwrite-init.gif b/docs/static/img/datasource-reference/appwrite/appwrite-init.gif
new file mode 100644
index 0000000000..c19119776d
Binary files /dev/null and b/docs/static/img/datasource-reference/appwrite/appwrite-init.gif differ
diff --git a/docs/static/img/datasource-reference/appwrite/appwrite-query.gif b/docs/static/img/datasource-reference/appwrite/appwrite-query.gif
new file mode 100644
index 0000000000..188d4fb52d
Binary files /dev/null and b/docs/static/img/datasource-reference/appwrite/appwrite-query.gif differ
diff --git a/plugins/package-lock.json b/plugins/package-lock.json
index 335593c495..4b20869651 100644
--- a/plugins/package-lock.json
+++ b/plugins/package-lock.json
@@ -9,6 +9,7 @@
"version": "0.0.1",
"dependencies": {
"@tooljet-plugins/airtable": "file:packages/airtable",
+ "@tooljet-plugins/appwrite": "file:packages/appwrite",
"@tooljet-plugins/bigquery": "file:packages/bigquery",
"@tooljet-plugins/common": "file:packages/common",
"@tooljet-plugins/couchdb": "file:packages/couchdb",
@@ -4494,6 +4495,10 @@
"resolved": "packages/airtable",
"link": true
},
+ "node_modules/@tooljet-plugins/appwrite": {
+ "resolved": "packages/appwrite",
+ "link": true
+ },
"node_modules/@tooljet-plugins/bigquery": {
"resolved": "packages/bigquery",
"link": true
@@ -12196,6 +12201,36 @@
"integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==",
"optional": true
},
+ "node_modules/node-appwrite": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/node-appwrite/-/node-appwrite-5.0.0.tgz",
+ "integrity": "sha512-VJ9e5+ra+ycQS17C0aJMbVXK4Gcja6at+f2EzlRlsjxAzTMetb79QJBOO6ktMtmVrUkAieMnaMZcV1hPppERmg==",
+ "dependencies": {
+ "axios": "^0.25.0",
+ "form-data": "^4.0.0"
+ }
+ },
+ "node_modules/node-appwrite/node_modules/axios": {
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz",
+ "integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==",
+ "dependencies": {
+ "follow-redirects": "^1.14.7"
+ }
+ },
+ "node_modules/node-appwrite/node_modules/form-data": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/node-fetch": {
"version": "2.6.7",
"license": "MIT",
@@ -16449,6 +16484,14 @@
"rimraf": "^3.0.2"
}
},
+ "packages/appwrite": {
+ "version": "1.0.0",
+ "dependencies": {
+ "@tooljet-plugins/common": "file:../common",
+ "node-appwrite": "^5.0.0",
+ "react": "^17.0.2"
+ }
+ },
"packages/bigquery": {
"name": "@tooljet-plugins/bigquery",
"version": "1.0.0",
@@ -20210,6 +20253,14 @@
"rimraf": "^3.0.2"
}
},
+ "@tooljet-plugins/appwrite": {
+ "version": "file:packages/appwrite",
+ "requires": {
+ "@tooljet-plugins/common": "file:../common",
+ "node-appwrite": "^5.0.0",
+ "react": "^17.0.2"
+ }
+ },
"@tooljet-plugins/bigquery": {
"version": "file:packages/bigquery",
"requires": {
@@ -25670,6 +25721,35 @@
"integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==",
"optional": true
},
+ "node-appwrite": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/node-appwrite/-/node-appwrite-5.0.0.tgz",
+ "integrity": "sha512-VJ9e5+ra+ycQS17C0aJMbVXK4Gcja6at+f2EzlRlsjxAzTMetb79QJBOO6ktMtmVrUkAieMnaMZcV1hPppERmg==",
+ "requires": {
+ "axios": "^0.25.0",
+ "form-data": "^4.0.0"
+ },
+ "dependencies": {
+ "axios": {
+ "version": "0.25.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz",
+ "integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==",
+ "requires": {
+ "follow-redirects": "^1.14.7"
+ }
+ },
+ "form-data": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "requires": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ }
+ }
+ }
+ },
"node-fetch": {
"version": "2.6.7",
"requires": {
diff --git a/plugins/package.json b/plugins/package.json
index 49afe9b688..7e2011332a 100644
--- a/plugins/package.json
+++ b/plugins/package.json
@@ -19,6 +19,7 @@
},
"dependencies": {
"@tooljet-plugins/airtable": "file:packages/airtable",
+ "@tooljet-plugins/appwrite": "file:packages/appwrite",
"@tooljet-plugins/bigquery": "file:packages/bigquery",
"@tooljet-plugins/common": "file:packages/common",
"@tooljet-plugins/couchdb": "file:packages/couchdb",
diff --git a/plugins/packages/appwrite/.gitignore b/plugins/packages/appwrite/.gitignore
new file mode 100644
index 0000000000..4c5f09d7c4
--- /dev/null
+++ b/plugins/packages/appwrite/.gitignore
@@ -0,0 +1,4 @@
+node_modules
+lib/*.d.*
+lib/*.js
+lib/*.js.map
\ No newline at end of file
diff --git a/plugins/packages/appwrite/README.md b/plugins/packages/appwrite/README.md
new file mode 100644
index 0000000000..e0f738f720
--- /dev/null
+++ b/plugins/packages/appwrite/README.md
@@ -0,0 +1,4 @@
+
+# Appwrite
+
+Documentation on: https://docs.tooljet.com/docs/data-sources/appwrite
\ No newline at end of file
diff --git a/plugins/packages/appwrite/__tests__/index.js b/plugins/packages/appwrite/__tests__/index.js
new file mode 100644
index 0000000000..d13e5cad99
--- /dev/null
+++ b/plugins/packages/appwrite/__tests__/index.js
@@ -0,0 +1,7 @@
+'use strict';
+
+const appwrite = require('../lib');
+
+describe('appwrite', () => {
+ it.todo('needs tests');
+});
diff --git a/plugins/packages/appwrite/lib/icon.svg b/plugins/packages/appwrite/lib/icon.svg
new file mode 100644
index 0000000000..e5030731d6
--- /dev/null
+++ b/plugins/packages/appwrite/lib/icon.svg
@@ -0,0 +1,9 @@
+
+
+
+
diff --git a/plugins/packages/appwrite/lib/index.ts b/plugins/packages/appwrite/lib/index.ts
new file mode 100644
index 0000000000..6a1bad8ced
--- /dev/null
+++ b/plugins/packages/appwrite/lib/index.ts
@@ -0,0 +1,91 @@
+import { QueryError, QueryResult, QueryService, ConnectionTestResult } from '@tooljet-plugins/common';
+import { SourceOptions, QueryOptions } from './types';
+import sdk from 'node-appwrite';
+import { bulkUpdate, createDocument, deleteDocument, getDocument, queryCollection, updateDocument } from './operations';
+const JSON5 = require('json5');
+
+export default class Appwrite implements QueryService {
+ async run(sourceOptions: SourceOptions, queryOptions: QueryOptions, dataSourceId: string): Promise {
+ const database = await this.getConnection(sourceOptions);
+ const operation = queryOptions.operation;
+ const body = this.returnObject(queryOptions.body);
+ let result = {};
+
+ try {
+ switch (operation) {
+ case 'list_docs':
+ result = await queryCollection(
+ database,
+ queryOptions.collectionId,
+ queryOptions.limit,
+ queryOptions.order_fields,
+ queryOptions.order_types,
+ queryOptions.where_field,
+ queryOptions.where_operation,
+ queryOptions.where_value
+ );
+ break;
+ case 'get_document':
+ result = await getDocument(database, queryOptions.collectionId, queryOptions.documentId);
+ break;
+ case 'add_document':
+ result = await createDocument(database, queryOptions.collectionId, body);
+ break;
+ case 'update_document':
+ result = await updateDocument(database, queryOptions.collectionId, queryOptions.documentId, body);
+ break;
+ case 'delete_document':
+ result = await deleteDocument(database, queryOptions.collectionId, queryOptions.documentId);
+ break;
+ case 'bulk_update':
+ result = await bulkUpdate(
+ database,
+ queryOptions.collectionId,
+ this.returnObject(queryOptions.records),
+ queryOptions['document_id_key']
+ );
+ break;
+ }
+ } catch (error) {
+ throw new QueryError('Query could not be completed', error.message, {});
+ }
+
+ return {
+ status: 'ok',
+ data: result,
+ };
+ }
+
+ private returnObject(data: any) {
+ if (!data) {
+ return {};
+ }
+ return typeof data === 'string' ? JSON5.parse(data) : data;
+ }
+
+ async getConnection(sourceOptions: SourceOptions, _options?: object): Promise {
+ const { host, secret_key, project_id } = sourceOptions;
+ const client = new sdk.Client();
+
+ client
+ .setEndpoint(host) // Your API Endpoint
+ .setProject(project_id) // Your project ID
+ .setKey(secret_key); // Your secret API key;
+
+ return new sdk.Database(client);
+ }
+
+ async testConnection(sourceOptions: SourceOptions): Promise {
+ const databaseClient = await this.getConnection(sourceOptions);
+
+ if (!databaseClient) {
+ throw new Error('Invalid credentials');
+ }
+
+ await databaseClient.listCollections();
+
+ return {
+ status: 'ok',
+ };
+ }
+}
diff --git a/plugins/packages/appwrite/lib/manifest.json b/plugins/packages/appwrite/lib/manifest.json
new file mode 100644
index 0000000000..fb04bd74b4
--- /dev/null
+++ b/plugins/packages/appwrite/lib/manifest.json
@@ -0,0 +1,44 @@
+
+{
+ "$schema": "https://raw.githubusercontent.com/ToolJet/ToolJet/develop/plugins/schemas/manifest.schema.json",
+ "title": "Appwrite datasource",
+ "description": "A schema defining Appwrite datasource",
+ "type": "api",
+ "source": {
+ "name": "Appwrite",
+ "kind": "appwrite",
+ "exposedVariables": {
+ "isLoading": false,
+ "data": {},
+ "rawData": {}
+ },
+ "options": {
+ "secret_key":{
+ "encrypted":true
+ }
+ }
+ },
+ "defaults": {},
+ "properties": {
+ "host": {
+ "label": "Host",
+ "key": "host",
+ "type": "text",
+ "description": "Appwrite database host/endpoint"
+ },
+ "project_id": {
+ "label": "Project ID",
+ "key": "project_id",
+ "type": "text",
+ "description": "Appwrite project id"
+ },
+ "secret_key": {
+ "label": "Secret Key",
+ "key": "secret_key",
+ "type": "textarea",
+ "encrypted": true,
+ "description": "Enter api key secret for appwrite account"
+ }
+ },
+ "required": ["host","secret_key","project_id"]
+}
\ No newline at end of file
diff --git a/plugins/packages/appwrite/lib/operations.json b/plugins/packages/appwrite/lib/operations.json
new file mode 100644
index 0000000000..253357d6dc
--- /dev/null
+++ b/plugins/packages/appwrite/lib/operations.json
@@ -0,0 +1,267 @@
+
+{
+ "$schema": "https://raw.githubusercontent.com/ToolJet/ToolJet/develop/plugins/schemas/operations.schema.json",
+ "title": "Appwrite datasource",
+ "description": "A schema defining Appwrite datasource",
+ "type": "api",
+ "defaults": {},
+ "properties": {
+ "operation": {
+ "label": "Operation",
+ "key": "operation",
+ "type": "dropdown-component-flip",
+ "description": "Single select dropdown for operation",
+ "list": [
+ {
+ "value": "list_docs",
+ "name": "List Documents"
+ },
+ {
+ "value": "get_document",
+ "name": "Get Document"
+ },
+ {
+ "value": "add_document",
+ "name": "Add Document to Collection"
+ },
+ {
+ "value": "update_document",
+ "name": "Update Document"
+ },
+ {
+ "value": "bulk_update",
+ "name": "Bulk update using document id"
+ },
+ {
+ "value": "delete_document",
+ "name": "Delete Document"
+ }
+ ]
+ },
+ "list_docs":{
+ "collectionId": {
+ "label": "Collection ID",
+ "key": "collectionId",
+ "type": "codehinter",
+ "lineNumbers": false,
+ "description": "Enter collection id",
+ "width": "320px",
+ "height": "36px",
+ "className": "codehinter-plugins col-6",
+ "placeholder": "Enter collection id"
+ },
+ "limit": {
+ "label": "Limit",
+ "key": "limit",
+ "type": "codehinter",
+ "lineNumbers": false,
+ "description": "Enter limit",
+ "width": "320px",
+ "height": "36px",
+ "className": "codehinter-plugins",
+ "placeholder": "Enter limit"
+ },
+ "order_fields": {
+ "label": "Order fields",
+ "key": "order_fields",
+ "type": "codehinter",
+ "lineNumbers": false,
+ "description": "Enter field names",
+ "height": "100px",
+ "className": "codehinter-plugins col-6",
+ "placeholder": "{{[ 'name','age' ]}}"
+ },
+ "order_types": {
+ "label": "Order types",
+ "key": "order_types",
+ "type": "codehinter",
+ "lineNumbers": false,
+ "description": "Enter respsctive order types",
+ "height": "100px",
+ "className": "codehinter-plugins col-6",
+ "placeholder": "{{[ 'DESC','ASC' ]}}"
+ },
+ "where_field": {
+ "label": "Field",
+ "key": "where_field",
+ "type": "codehinter",
+ "lineNumbers": false,
+ "description": "Enter field",
+ "height": "36px",
+ "className": "codehinter-plugins col-4",
+ "placeholder": "Enter field"
+ },
+ "where_operation": {
+ "label": "Operator",
+ "key": "where_operation",
+ "className": "col-4",
+ "type": "dropdown",
+ "description": "Single select dropdown for where operation",
+ "list": [
+ {
+ "value": "==",
+ "name": "=="
+ },
+ {
+ "value": "!=",
+ "name": "!="
+ },
+ {
+ "value": "<",
+ "name": "<"
+ },
+ {
+ "value": ">",
+ "name": ">"
+ },
+ {
+ "value": "<=",
+ "name": "<="
+ },
+ {
+ "value": ">=",
+ "name": ">="
+ },
+ {
+ "value": "",
+ "name": "None"
+ }
+ ]
+ },
+ "where_value": {
+ "label": "Value",
+ "key": "where_value",
+ "type": "codehinter",
+ "lineNumbers": false,
+ "description": "Enter value",
+ "height": "36px",
+ "className": "codehinter-plugins col-4",
+ "placeholder": "Enter value"
+ }
+ },
+ "get_document": {
+ "collectionId": {
+ "label": "Collection ID",
+ "key": "collectionId",
+ "type": "codehinter",
+ "lineNumbers": false,
+ "description": "Enter collection id",
+ "height": "36px",
+ "className": "codehinter-plugins col-6",
+ "placeholder": "Enter collection id"
+ },
+ "documentId": {
+ "label": "Document ID",
+ "key": "documentId",
+ "type": "codehinter",
+ "lineNumbers": false,
+ "description": "Enter document id",
+ "height": "36px",
+ "className": "codehinter-plugins col-6",
+ "placeholder": "Enter document id"
+ }
+ },
+ "add_document": {
+ "collectionId": {
+ "label": "Collection ID",
+ "key": "collectionId",
+ "type": "codehinter",
+ "lineNumbers": false,
+ "description": "Enter collection id",
+ "height": "36px",
+ "className": "codehinter-plugins col-6",
+ "placeholder": "Enter collection id"
+ },
+ "body": {
+ "label": "Body",
+ "key": "body",
+ "type": "codehinter",
+ "description": "Enter document body",
+ "height": "150px"
+ }
+ },
+ "update_document": {
+ "collectionId": {
+ "label": "Collection ID",
+ "key": "collectionId",
+ "type": "codehinter",
+ "lineNumbers": false,
+ "description": "Enter collection id",
+ "height": "36px",
+ "className": "codehinter-plugins col-6",
+ "placeholder": "Enter collection id"
+ },
+ "documentId": {
+ "label": "Document ID",
+ "key": "documentId",
+ "type": "codehinter",
+ "lineNumbers": false,
+ "description": "Enter document id",
+ "height": "36px",
+ "className": "codehinter-plugins col-6",
+ "placeholder": "Enter document id"
+ },
+ "body": {
+ "label": "Body",
+ "key": "body",
+ "type": "codehinter",
+ "description": "Enter document body",
+ "height": "150px"
+ }
+ },
+ "delete_document": {
+ "collectionId": {
+ "label": "Collection ID",
+ "key": "collectionId",
+ "type": "codehinter",
+ "lineNumbers": false,
+ "description": "Enter collection id",
+ "height": "36px",
+ "className": "codehinter-plugins col-6",
+ "placeholder": "Enter collection id"
+ },
+ "documentId": {
+ "label": "Document ID",
+ "key": "documentId",
+ "type": "codehinter",
+ "lineNumbers": false,
+ "description": "Enter document id",
+ "height": "36px",
+ "className": "codehinter-plugins col-6",
+ "placeholder": "Enter document id"
+ }
+ },
+ "bulk_update": {
+ "collectionId": {
+ "label": "Collection ID",
+ "key": "collectionId",
+ "type": "codehinter",
+ "lineNumbers": false,
+ "description": "Enter collection id",
+ "width": "320px",
+ "height": "36px",
+ "className": "codehinter-plugins",
+ "placeholder": "Enter collection id"
+ },
+ "document_id_key": {
+ "label": "Key for document Id",
+ "key": "document_id_key",
+ "type": "codehinter",
+ "lineNumbers": false,
+ "description": "Enter key for document Id",
+ "width": "320px",
+ "height": "36px",
+ "className": "codehinter-plugins",
+ "placeholder": "Enter key for document Id"
+ },
+ "records": {
+ "label": "Records",
+ "key": "records",
+ "type": "codehinter",
+ "mode": "javascript",
+ "description": "Enter records",
+ "height": "150px"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugins/packages/appwrite/lib/operations.ts b/plugins/packages/appwrite/lib/operations.ts
new file mode 100644
index 0000000000..53ed36042f
--- /dev/null
+++ b/plugins/packages/appwrite/lib/operations.ts
@@ -0,0 +1,88 @@
+import { Database, Query } from 'node-appwrite';
+
+function computeValue(value: string) {
+ const numConverted = Number.parseInt(value);
+ return isNaN(numConverted) ? value : numConverted;
+}
+
+export async function queryCollection(
+ db: Database,
+ collection: string,
+ limit: number,
+ order_fields: string[],
+ order_types: string[],
+ where_field: string,
+ where_operation: string,
+ where_value: any
+): Promise