diff --git a/packages/backend/src/apps/dropbox/actions/create-folder/index.ts b/packages/backend/src/apps/dropbox/actions/create-folder/index.ts
new file mode 100644
index 00000000..181977cc
--- /dev/null
+++ b/packages/backend/src/apps/dropbox/actions/create-folder/index.ts
@@ -0,0 +1,36 @@
+import path from 'node:path';
+import defineAction from '../../../../helpers/define-action';
+
+export default defineAction({
+ name: 'Create folder',
+ key: 'createFolder',
+ description: 'Create a new folder with the given parent folder and folder name',
+ arguments: [
+ {
+ label: 'Folder',
+ key: 'parentFolder',
+ type: 'string' as const,
+ required: true,
+ description: 'Enter the parent folder path, like /TextFiles/ or /Documents/Taxes/',
+ variables: true,
+ },
+ {
+ label: 'Folder Name',
+ key: 'folderName',
+ type: 'string' as const,
+ required: true,
+ description: 'Enter the name for the new folder',
+ variables: true,
+ },
+ ],
+
+ async run($) {
+ const parentFolder = $.step.parameters.parentFolder as string;
+ const folderName = $.step.parameters.folderName as string;
+ const folderPath = path.join(parentFolder, folderName);
+
+ const response = await $.http.post('/2/files/create_folder_v2', { path: folderPath });
+
+ $.setActionItem({ raw: response.data });
+ },
+});
diff --git a/packages/backend/src/apps/dropbox/actions/index.ts b/packages/backend/src/apps/dropbox/actions/index.ts
new file mode 100644
index 00000000..14f401da
--- /dev/null
+++ b/packages/backend/src/apps/dropbox/actions/index.ts
@@ -0,0 +1,4 @@
+import createFolder from "./create-folder";
+import renameFile from "./rename-file";
+
+export default [createFolder, renameFile];
diff --git a/packages/backend/src/apps/dropbox/actions/rename-file/index.ts b/packages/backend/src/apps/dropbox/actions/rename-file/index.ts
new file mode 100644
index 00000000..76bacffc
--- /dev/null
+++ b/packages/backend/src/apps/dropbox/actions/rename-file/index.ts
@@ -0,0 +1,45 @@
+import path from 'node:path';
+import defineAction from '../../../../helpers/define-action';
+
+export default defineAction({
+ name: 'Rename file',
+ key: 'renameFile',
+ description: 'Rename a file with the given file path and new name',
+ arguments: [
+ {
+ label: 'File Path',
+ key: 'filePath',
+ type: 'string' as const,
+ required: true,
+ description:
+ 'Write the full path to the file such as /Folder1/File.pdf',
+ variables: true,
+ },
+ {
+ label: 'New Name',
+ key: 'newName',
+ type: 'string' as const,
+ required: true,
+ description: "Enter the new name for the file (without the extension, e.g., '.pdf')",
+ variables: true,
+ },
+ ],
+
+ async run($) {
+ const filePath = $.step.parameters.filePath as string;
+ const newName = $.step.parameters.newName as string;
+ const fileObject = path.parse(filePath);
+ const newPath = path.format({
+ dir: fileObject.dir,
+ ext: fileObject.ext,
+ name: newName,
+ });
+
+ const response = await $.http.post('/2/files/move_v2', {
+ from_path: filePath,
+ to_path: newPath,
+ });
+
+ $.setActionItem({ raw: response.data.metadata });
+ },
+});
diff --git a/packages/backend/src/apps/dropbox/assets/favicon.svg b/packages/backend/src/apps/dropbox/assets/favicon.svg
new file mode 100644
index 00000000..59f38626
--- /dev/null
+++ b/packages/backend/src/apps/dropbox/assets/favicon.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/packages/backend/src/apps/dropbox/auth/generate-auth-url.ts b/packages/backend/src/apps/dropbox/auth/generate-auth-url.ts
new file mode 100644
index 00000000..d663c25c
--- /dev/null
+++ b/packages/backend/src/apps/dropbox/auth/generate-auth-url.ts
@@ -0,0 +1,22 @@
+import { URLSearchParams } from 'url';
+import { IField, IGlobalVariable } from '@automatisch/types';
+import scopes from '../common/scopes';
+
+export default async function generateAuthUrl($: IGlobalVariable) {
+ const oauthRedirectUrlField = $.app.auth.fields.find(
+ (field: IField) => field.key == 'oAuthRedirectUrl'
+ );
+ const callbackUrl = oauthRedirectUrlField.value as string;
+
+ const searchParams = new URLSearchParams({
+ client_id: $.auth.data.clientId as string,
+ redirect_uri: callbackUrl,
+ response_type: 'code',
+ scope: scopes.join(' '),
+ token_access_type: 'offline',
+ });
+
+ const url = `${$.app.baseUrl}/oauth2/authorize?${searchParams.toString()}`;
+
+ await $.auth.set({ url });
+}
diff --git a/packages/backend/src/apps/dropbox/auth/index.ts b/packages/backend/src/apps/dropbox/auth/index.ts
new file mode 100644
index 00000000..4331302b
--- /dev/null
+++ b/packages/backend/src/apps/dropbox/auth/index.ts
@@ -0,0 +1,48 @@
+import generateAuthUrl from './generate-auth-url';
+import verifyCredentials from './verify-credentials';
+import isStillVerified from './is-still-verified';
+import refreshToken from './refresh-token';
+
+export default {
+ fields: [
+ {
+ key: 'oAuthRedirectUrl',
+ label: 'OAuth Redirect URL',
+ type: 'string' as const,
+ required: true,
+ readOnly: true,
+ value: '{WEB_APP_URL}/app/dropbox/connections/add',
+ placeholder: null,
+ description:
+ 'When asked to input an OAuth callback or redirect URL in Dropbox OAuth, enter the URL above.',
+ clickToCopy: true,
+ },
+ {
+ key: 'clientId',
+ label: 'App Key',
+ type: 'string' as const,
+ required: true,
+ readOnly: false,
+ value: null,
+ placeholder: null,
+ description: null,
+ clickToCopy: false,
+ },
+ {
+ key: 'clientSecret',
+ label: 'App Secret',
+ type: 'string' as const,
+ required: true,
+ readOnly: false,
+ value: null,
+ placeholder: null,
+ description: null,
+ clickToCopy: false,
+ },
+ ],
+
+ generateAuthUrl,
+ verifyCredentials,
+ isStillVerified,
+ refreshToken,
+};
diff --git a/packages/backend/src/apps/dropbox/auth/is-still-verified.ts b/packages/backend/src/apps/dropbox/auth/is-still-verified.ts
new file mode 100644
index 00000000..bf70f874
--- /dev/null
+++ b/packages/backend/src/apps/dropbox/auth/is-still-verified.ts
@@ -0,0 +1,9 @@
+import { IGlobalVariable } from '@automatisch/types';
+import getCurrentAccount from '../common/get-current-account';
+
+const isStillVerified = async ($: IGlobalVariable) => {
+ const account = await getCurrentAccount($);
+ return !!account;
+};
+
+export default isStillVerified;
diff --git a/packages/backend/src/apps/dropbox/auth/refresh-token.ts b/packages/backend/src/apps/dropbox/auth/refresh-token.ts
new file mode 100644
index 00000000..67c4454f
--- /dev/null
+++ b/packages/backend/src/apps/dropbox/auth/refresh-token.ts
@@ -0,0 +1,41 @@
+import { Buffer } from 'node:buffer';
+import { IGlobalVariable } from '@automatisch/types';
+
+const refreshToken = async ($: IGlobalVariable) => {
+ const params = {
+ grant_type: 'refresh_token',
+ refresh_token: $.auth.data.refreshToken as string,
+ };
+
+ const basicAuthToken = Buffer
+ .from(`${$.auth.data.clientId}:${$.auth.data.clientSecret}`)
+ .toString('base64');
+
+ const { data } = await $.http.post(
+ 'oauth2/token',
+ null,
+ {
+ params,
+ headers: {
+ Authorization: `Basic ${basicAuthToken}`
+ },
+ additionalProperties: {
+ skipAddingAuthHeader: true
+ }
+ }
+ );
+
+ const {
+ access_token: accessToken,
+ expires_in: expiresIn,
+ token_type: tokenType,
+ } = data;
+
+ await $.auth.set({
+ accessToken,
+ expiresIn,
+ tokenType,
+ });
+};
+
+export default refreshToken;
diff --git a/packages/backend/src/apps/dropbox/auth/verify-credentials.ts b/packages/backend/src/apps/dropbox/auth/verify-credentials.ts
new file mode 100644
index 00000000..f56b25d2
--- /dev/null
+++ b/packages/backend/src/apps/dropbox/auth/verify-credentials.ts
@@ -0,0 +1,102 @@
+import { IGlobalVariable, IField } from '@automatisch/types';
+import getCurrentAccount from '../common/get-current-account';
+
+type TAccount = {
+ account_id: string,
+ name: {
+ given_name: string,
+ surname: string,
+ familiar_name: string,
+ display_name: string,
+ abbreviated_name: string,
+ },
+ email: string,
+ email_verified: boolean,
+ disabled: boolean,
+ country: string,
+ locale: string,
+ referral_link: string,
+ is_paired: boolean,
+ account_type: {
+ ".tag": string,
+ },
+ root_info: {
+ ".tag": string,
+ root_namespace_id: string,
+ home_namespace_id: string,
+ },
+}
+
+const verifyCredentials = async ($: IGlobalVariable) => {
+ const oauthRedirectUrlField = $.app.auth.fields.find(
+ (field: IField) => field.key == 'oAuthRedirectUrl'
+ );
+ const redirectUrl = oauthRedirectUrlField.value as string;
+ const params = {
+ client_id: $.auth.data.clientId as string,
+ redirect_uri: redirectUrl,
+ client_secret: $.auth.data.clientSecret as string,
+ code: $.auth.data.code as string,
+ grant_type: 'authorization_code',
+ }
+ const { data: verifiedCredentials } = await $.http.post(
+ '/oauth2/token',
+ null,
+ { params }
+ );
+
+ const {
+ access_token: accessToken,
+ refresh_token: refreshToken,
+ expires_in: expiresIn,
+ scope: scope,
+ token_type: tokenType,
+ account_id: accountId,
+ team_id: teamId,
+ id_token: idToken,
+ uid,
+ } = verifiedCredentials;
+
+ await $.auth.set({
+ accessToken,
+ refreshToken,
+ expiresIn,
+ scope,
+ tokenType,
+ accountId,
+ teamId,
+ idToken,
+ uid
+ });
+
+ const account = await getCurrentAccount($) as TAccount;
+
+ await $.auth.set({
+ accountId: account.account_id,
+ name: {
+ givenName: account.name.given_name,
+ surname: account.name.surname,
+ familiarName: account.name.familiar_name,
+ displayName: account.name.display_name,
+ abbreviatedName: account.name.abbreviated_name,
+ },
+ email: account.email,
+ emailVerified: account.email_verified,
+ disabled: account.disabled,
+ country: account.country,
+ locale: account.locale,
+ referralLink: account.referral_link,
+ isPaired: account.is_paired,
+ accountType: {
+ ".tag": account.account_type['.tag'],
+ },
+ rootInfo: {
+ ".tag": account.root_info['.tag'],
+ rootNamespaceId: account.root_info.root_namespace_id,
+ homeNamespaceId: account.root_info.home_namespace_id,
+ },
+ screenName: `${account.name.display_name} - ${account.email}`,
+ });
+};
+
+export default verifyCredentials;
diff --git a/packages/backend/src/apps/dropbox/common/add-auth-header.ts b/packages/backend/src/apps/dropbox/common/add-auth-header.ts
new file mode 100644
index 00000000..ac7b7231
--- /dev/null
+++ b/packages/backend/src/apps/dropbox/common/add-auth-header.ts
@@ -0,0 +1,13 @@
+import { TBeforeRequest } from '@automatisch/types';
+
+const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
+ requestConfig.headers['Content-Type'] = 'application/json';
+
+ if (!requestConfig.additionalProperties?.skipAddingAuthHeader && $.auth.data?.accessToken) {
+ requestConfig.headers.Authorization = `Bearer ${$.auth.data.accessToken}`;
+ }
+
+ return requestConfig;
+};
+
+export default addAuthHeader;
diff --git a/packages/backend/src/apps/dropbox/common/get-current-account.ts b/packages/backend/src/apps/dropbox/common/get-current-account.ts
new file mode 100644
index 00000000..6764e781
--- /dev/null
+++ b/packages/backend/src/apps/dropbox/common/get-current-account.ts
@@ -0,0 +1,8 @@
+import { IGlobalVariable, IJSONObject } from '@automatisch/types';
+
+const getCurrentAccount = async ($: IGlobalVariable): Promise => {
+ const response = await $.http.post('/2/users/get_current_account', null);
+ return response.data;
+};
+
+export default getCurrentAccount;
diff --git a/packages/backend/src/apps/dropbox/common/scopes.ts b/packages/backend/src/apps/dropbox/common/scopes.ts
new file mode 100644
index 00000000..b257d7cb
--- /dev/null
+++ b/packages/backend/src/apps/dropbox/common/scopes.ts
@@ -0,0 +1,8 @@
+const scopes = [
+ 'account_info.read',
+ 'files.metadata.read',
+ 'files.content.write',
+ 'files.content.read',
+];
+
+export default scopes;
diff --git a/packages/backend/src/apps/dropbox/index.d.ts b/packages/backend/src/apps/dropbox/index.d.ts
new file mode 100644
index 00000000..e69de29b
diff --git a/packages/backend/src/apps/dropbox/index.ts b/packages/backend/src/apps/dropbox/index.ts
new file mode 100644
index 00000000..d7bc877b
--- /dev/null
+++ b/packages/backend/src/apps/dropbox/index.ts
@@ -0,0 +1,18 @@
+import defineApp from '../../helpers/define-app';
+import addAuthHeader from './common/add-auth-header';
+import auth from './auth';
+import actions from './actions';
+
+export default defineApp({
+ name: 'Dropbox',
+ key: 'dropbox',
+ iconUrl: '{BASE_URL}/apps/dropbox/assets/favicon.svg',
+ authDocUrl: 'https://automatisch.io/docs/apps/dropbox/connection',
+ supportsConnections: true,
+ baseUrl: 'https://dropbox.com',
+ apiBaseUrl: 'https://api.dropboxapi.com',
+ primaryColor: '0061ff',
+ beforeRequest: [addAuthHeader],
+ auth,
+ actions,
+});
diff --git a/packages/backend/src/apps/spotify/auth/refresh-token.ts b/packages/backend/src/apps/spotify/auth/refresh-token.ts
index dc20820a..b748f658 100644
--- a/packages/backend/src/apps/spotify/auth/refresh-token.ts
+++ b/packages/backend/src/apps/spotify/auth/refresh-token.ts
@@ -1,3 +1,4 @@
+import { Buffer } from 'node:buffer';
import { IGlobalVariable } from '@automatisch/types';
const refreshToken = async ($: IGlobalVariable) => {
diff --git a/packages/docs/pages/.vitepress/config.js b/packages/docs/pages/.vitepress/config.js
index 1118ac41..85915902 100644
--- a/packages/docs/pages/.vitepress/config.js
+++ b/packages/docs/pages/.vitepress/config.js
@@ -59,6 +59,15 @@ export default defineConfig({
{ text: 'Connection', link: '/apps/discord/connection' },
],
},
+ {
+ text: 'Dropbox',
+ collapsible: true,
+ collapsed: true,
+ items: [
+ { text: 'Actions', link: '/apps/dropbox/actions' },
+ { text: 'Connection', link: '/apps/dropbox/connection' },
+ ],
+ },
{
text: 'Flickr',
collapsible: true,
diff --git a/packages/docs/pages/apps/dropbox/actions.md b/packages/docs/pages/apps/dropbox/actions.md
new file mode 100644
index 00000000..4d5d4b3e
--- /dev/null
+++ b/packages/docs/pages/apps/dropbox/actions.md
@@ -0,0 +1,14 @@
+---
+favicon: /favicons/dropbox.svg
+items:
+ - name: Create a folder
+ desc: Creates a new folder with the given parent folder and folder name.
+ - name: Rename a file
+ desc: Rename a file with the given file path and new name.
+---
+
+
+
+
diff --git a/packages/docs/pages/apps/dropbox/connection.md b/packages/docs/pages/apps/dropbox/connection.md
new file mode 100644
index 00000000..88696142
--- /dev/null
+++ b/packages/docs/pages/apps/dropbox/connection.md
@@ -0,0 +1,20 @@
+# Dropbox
+
+:::info
+This page explains the steps you need to follow to set up the Dropbox
+connection in Automatisch. If any of the steps are outdated, please let us know!
+:::
+
+1. Go to the [link](https://www.dropbox.com/developers/apps) to create a **new application** on Dropbox.
+1. Choose the "Scoped access" option in the "Choose an API" section.
+1. Choose the "Full Dropbox" option in the "Choose the type of access you need" section.
+1. Name your application.
+1. Click on the **Create app** button.
+1. Copy **OAuth Redirect URL** from Automatisch to **Redirect URIs** field and add it.
+1. Click on the **Scoped App** link in the "Permission type" section.
+1. Check the checkbox for the "files.content.write" scope and click on the **Submit** button.
+1. Go back to the "Settings" tab.
+1. Copy **App key** to **App key** field on Automatisch.
+1. Copy **App secret** to **App secret** field on Automatisch.
+1. Click **Submit** button on Automatisch.
+1. Congrats! Start using your new Dropbox connection within the flows.
diff --git a/packages/docs/pages/guide/available-apps.md b/packages/docs/pages/guide/available-apps.md
index 47f92456..1909c0f4 100644
--- a/packages/docs/pages/guide/available-apps.md
+++ b/packages/docs/pages/guide/available-apps.md
@@ -9,9 +9,10 @@ Following integrations are currently supported by Automatisch.
- [DeepL](/apps/deepl/actions)
- [Delay](/apps/delay/actions)
- [Discord](/apps/discord/actions)
+- [Dropbox](/apps/dropbox/actions)
- [Flickr](/apps/flickr/triggers)
-- [Google Drive](/apps/google-drive/triggers)
- [Github](/apps/github/triggers)
+- [Google Drive](/apps/google-drive/triggers)
- [Google Forms](/apps/google-forms/triggers)
- [HTTP Request](/apps/http-request/actions)
- [Ntfy](/apps/ntfy/actions)
diff --git a/packages/docs/pages/public/favicons/dropbox.svg b/packages/docs/pages/public/favicons/dropbox.svg
new file mode 100644
index 00000000..59f38626
--- /dev/null
+++ b/packages/docs/pages/public/favicons/dropbox.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file