diff --git a/.env.example b/.env.example index 5ed292fe6..0c4fe65b2 100644 --- a/.env.example +++ b/.env.example @@ -112,6 +112,9 @@ BOX_FILESTORAGE_CLOUD_CLIENT_SECRET= # Onedrive ONEDRIVE_FILESTORAGE_CLOUD_CLIENT_ID= ONEDRIVE_FILESTORAGE_CLOUD_CLIENT_SECRET= +# dropbox +DROPBOX_FILESTORAGE_CLOUD_CLIENT_ID= +DROPBOX_FILESTORAGE_CLOUD_CLIENT_SECRET= # Google Drive GOOGLEDRIVE_FILESTORAGE_CLOUD_CLIENT_ID= diff --git a/packages/api/scripts/init.sql b/packages/api/scripts/init.sql index d242e8935..43efca349 100644 --- a/packages/api/scripts/init.sql +++ b/packages/api/scripts/init.sql @@ -552,7 +552,10 @@ CREATE TABLE connector_sets ats_ashby boolean NULL, ecom_webflow boolean NULL, crm_microsoftdynamicssales boolean NULL, + fs_dropbox boolean NULL, fs_googledrive boolean NULL, + fs_sharepoint boolean NULL, + fs_onedrive boolean NULL, CONSTRAINT PK_project_connector PRIMARY KEY ( id_connector_set ) ); diff --git a/packages/api/scripts/seed.sql b/packages/api/scripts/seed.sql index bfb0bd6b4..dc087a70d 100644 --- a/packages/api/scripts/seed.sql +++ b/packages/api/scripts/seed.sql @@ -1,10 +1,11 @@ INSERT INTO users (id_user, identification_strategy, email, password_hash, first_name, last_name) VALUES ('0ce39030-2901-4c56-8db0-5e326182ec6b', 'b2c','local@panora.dev', '$2b$10$Y7Q8TWGyGuc5ecdIASbBsuXMo3q/Rs3/cnY.mLZP4tUgfGUOCUBlG', 'local', 'Panora'); -INSERT INTO connector_sets (id_connector_set, crm_hubspot, crm_zoho, crm_pipedrive, crm_attio, crm_zendesk, crm_close, tcg_zendesk, tcg_gorgias, tcg_front, tcg_jira, tcg_gitlab, fs_box, tcg_github, hris_deel, hris_sage, ats_ashby, crm_microsoftdynamicssales, ecom_webflow, tcg_linear, ecom_shopify, ecom_woocommerce, ecom_amazon, ecom_squarespace, hris_gusto, fs_googledrive) VALUES - ('1709da40-17f7-4d3a-93a0-96dc5da6ddd7', TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE), - ('852dfff8-ab63-4530-ae49-e4b2924407f8', TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE), - ('aed0f856-f802-4a79-8640-66d441581a99', TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE); + +INSERT INTO connector_sets (id_connector_set, crm_hubspot, crm_zoho, crm_pipedrive, crm_attio, crm_zendesk, crm_close, tcg_zendesk, tcg_gorgias, tcg_front, tcg_jira, tcg_gitlab, fs_box, tcg_github, hris_deel, hris_sage, ats_ashby, crm_microsoftdynamicssales, ecom_webflow, tcg_linear, ecom_shopify, ecom_woocommerce, ecom_amazon, ecom_squarespace, hris_gusto, fs_googledrive, fs_dropbox, fs_sharepoint, fs_onedrive) VALUES + ('1709da40-17f7-4d3a-93a0-96dc5da6ddd7', TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE), + ('852dfff8-ab63-4530-ae49-e4b2924407f8', TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE), + ('aed0f856-f802-4a79-8640-66d441581a99', TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE); INSERT INTO projects (id_project, name, sync_mode, id_user, id_connector_set) VALUES ('1e468c15-aa57-4448-aa2b-7fed640d1e3d', 'Project 1', 'pull', '0ce39030-2901-4c56-8db0-5e326182ec6b', '1709da40-17f7-4d3a-93a0-96dc5da6ddd7'), diff --git a/packages/api/src/@core/utils/types/original/original.file-storage.ts b/packages/api/src/@core/utils/types/original/original.file-storage.ts index d3d3eaa7a..d348a9fde 100644 --- a/packages/api/src/@core/utils/types/original/original.file-storage.ts +++ b/packages/api/src/@core/utils/types/original/original.file-storage.ts @@ -1,3 +1,16 @@ +import { DropboxGroupInput, DropboxGroupOutput } from '@filestorage/group/services/dropbox/types'; + +import { DropboxUserInput, DropboxUserOutput } from '@filestorage/user/services/dropbox/types'; + +import { DropboxFileInput, DropboxFileOutput } from '@filestorage/file/services/dropbox/types'; + +import { DropboxFolderInput, DropboxFolderOutput } from '@filestorage/folder/services/dropbox/types'; + +import { + BoxSharedLinkInput, + BoxSharedLinkOutput, +} from '@filestorage/sharedlink/services/box/types'; + /* INPUT */ import { @@ -99,9 +112,13 @@ import { GoogleDriveFolderInput, GoogleDriveFolderOutput } from '@filestorage/fo import { GoogleDriveFileInput, GoogleDriveFileOutput } from '@filestorage/file/services/googledrive/types'; /* file */ + +/* folder */ export type OriginalFileInput = | BoxFileInput | OnedriveFileInput + | SharepointFileInput; + | DropboxFileInput; | SharepointFileInput | GoogleDriveFileInput; @@ -109,6 +126,8 @@ export type OriginalFileInput = export type OriginalFolderInput = | BoxFolderInput | OnedriveFolderInput + | SharepointFolderInput; + | DropboxFolderInput; | SharepointFolderInput | GoogleDriveFolderInput; @@ -125,16 +144,20 @@ export type OriginalSharedLinkInput = any; export type OriginalDriveInput = GoogleDriveDriveInput | OnedriveDriveInput | SharepointDriveInput; /* group */ + +/* user */ export type OriginalGroupInput = | BoxGroupInput | OnedriveGroupInput - | SharepointGroupInput; + | SharepointGroupInput + | DropboxGroupInput; /* user */ export type OriginalUserInput = | BoxUserInput | OnedriveUserInput - | SharepointUserInput; + | SharepointUserInput + | DropboxUserInput; export type FileStorageObjectInput = | OriginalFileInput @@ -148,9 +171,13 @@ export type FileStorageObjectInput = /* OUTPUT */ /* file */ + +/* folder */ export type OriginalFileOutput = | BoxFileOutput | OnedriveFileOutput + | SharepointFileOutput; + | DropboxFileOutput; | SharepointFileOutput | GoogleDriveFileOutput; @@ -158,6 +185,8 @@ export type OriginalFileOutput = export type OriginalFolderOutput = | BoxFolderOutput | OnedriveFolderOutput + | SharepointFolderOutput; + | DropboxFolderOutput; | SharepointFolderOutput | GoogleDriveFolderOutput; @@ -174,16 +203,20 @@ export type OriginalSharedLinkOutput = any; export type OriginalDriveOutput = GoogleDriveDriveOutput | OnedriveDriveOutput | SharepointDriveOutput; /* group */ + +/* user */ export type OriginalGroupOutput = | BoxGroupOutput | OnedriveGroupOutput - | SharepointGroupOutput; + | SharepointGroupOutput + | DropboxGroupOutput; /* user */ export type OriginalUserOutput = | BoxUserOutput | OnedriveUserOutput - | SharepointUserOutput; + | SharepointUserOutput + | DropboxUserOutput; export type FileStorageObjectOutput = | OriginalFileOutput diff --git a/packages/api/src/filestorage/file/file.module.ts b/packages/api/src/filestorage/file/file.module.ts index 6c797c510..e4166e39e 100644 --- a/packages/api/src/filestorage/file/file.module.ts +++ b/packages/api/src/filestorage/file/file.module.ts @@ -1,3 +1,10 @@ +import { DropboxFileMapper } from './services/dropbox/mappers'; +import { DropboxService } from './services/dropbox'; +import { SharepointFileMapper } from './services/sharepoint/mappers'; +import { SharepointService } from './services/sharepoint'; +import { OnedriveFileMapper } from './services/onedrive/mappers'; +import { OnedriveService } from './services/onedrive'; +import { BullQueueModule } from '@@core/@core-services/queues/queue.module'; import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; import { WebhookService } from '@@core/@core-services/webhooks/panora-webhooks/webhook.service'; import { Utils } from '@filestorage/@lib/@utils'; @@ -8,8 +15,6 @@ import { BoxFileMapper } from './services/box/mappers'; import { FileService } from './services/file.service'; import { GoogleDriveService } from './services/googledrive'; import { GoogleDriveFileMapper } from './services/googledrive/mappers'; -import { OnedriveService } from './services/onedrive'; -import { OnedriveFileMapper } from './services/onedrive/mappers'; import { ServiceRegistry } from './services/registry.service'; import { SharepointService } from './services/sharepoint'; import { SharepointFileMapper } from './services/sharepoint/mappers'; @@ -33,6 +38,9 @@ import { SyncService } from './sync/sync.service'; SharepointService, SharepointFileMapper, OnedriveService, + OnedriveFileMapper, + DropboxService, + DropboxFileMapper, GoogleDriveService, ], exports: [SyncService, ServiceRegistry], diff --git a/packages/api/src/filestorage/file/services/dropbox/index.ts b/packages/api/src/filestorage/file/services/dropbox/index.ts new file mode 100644 index 000000000..e6da90b7b --- /dev/null +++ b/packages/api/src/filestorage/file/services/dropbox/index.ts @@ -0,0 +1,113 @@ +import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; +import { LoggerService } from '@@core/@core-services/logger/logger.service'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; +import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; +import { FileStorageObject } from '@filestorage/@lib/@types'; +import { IFileService } from '@filestorage/file/types'; +import { Injectable } from '@nestjs/common'; +import axios from 'axios'; +import { ServiceRegistry } from '../registry.service'; +import { DropboxFileOutput } from './types'; +import { UnifiedFilestorageFolderOutput } from '@filestorage/folder/types/model.unified'; + +@Injectable() +export class DropboxService implements IFileService { + constructor( + private prisma: PrismaService, + private logger: LoggerService, + private cryptoService: EncryptionService, + private registry: ServiceRegistry, + ) { + this.logger.setContext( + FileStorageObject.file.toUpperCase() + ':' + DropboxService.name, + ); + this.registry.registerService('dropbox', this); + } + + async getAllFilesInFolder( + folderPath: string, + connection: any, + ): Promise { + // ref: https://www.dropbox.com/developers/documentation/http/documentation#files-list_folder + const files: DropboxFileOutput[] = []; + let cursor: string | null = null; + let hasMore = true; + + while (hasMore) { + const url = cursor + ? `${connection.account_url}/files/list_folder/continue` + : `${connection.account_url}/files/list_folder`; + + const data = cursor ? { cursor } : { path: folderPath, recursive: false }; + + try { + const response = await axios.post(url, data, { + headers: { + Authorization: `Bearer ${this.cryptoService.decrypt( + connection.access_token, + )}`, + 'Content-Type': 'application/json', + }, + }); + + const { entries, has_more, cursor: newCursor } = response.data; + + // Collect all file entries + files.push(...entries.filter((entry: any) => entry['.tag'] === 'file')); + + hasMore = has_more; + cursor = newCursor; + } catch (error) { + console.error('Error listing files in folder:', error); + throw new Error('Failed to list all files in the folder.'); + } + } + + return files; + } + + async sync(data: SyncParam): Promise> { + try { + const { linkedUserId, id_folder } = data; + if (!id_folder) return; + + const connection = await this.prisma.connections.findFirst({ + where: { + id_linked_user: linkedUserId, + provider_slug: 'dropbox', + vertical: 'filestorage', + }, + }); + + const folder = await this.prisma.fs_folders.findUnique({ + where: { + id_fs_folder: id_folder as string, + }, + }); + + const remote_data = await this.prisma.remote_data.findFirst({ + where: { + ressource_owner_id: folder.id_fs_folder, + }, + }); + + const folder_remote_data = JSON.parse(remote_data.data); + + const files = await this.getAllFilesInFolder( + folder_remote_data.path_display, + connection, + ); + + this.logger.log(`Synced dropbox files !`); + + return { + data: files, + message: 'Dropbox files retrieved', + statusCode: 200, + }; + } catch (error) { + throw error; + } + } +} diff --git a/packages/api/src/filestorage/file/services/dropbox/mappers.ts b/packages/api/src/filestorage/file/services/dropbox/mappers.ts new file mode 100644 index 000000000..686601082 --- /dev/null +++ b/packages/api/src/filestorage/file/services/dropbox/mappers.ts @@ -0,0 +1,97 @@ +import { MappersRegistry } from '@@core/@core-services/registries/mappers.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { OriginalSharedLinkOutput } from '@@core/utils/types/original/original.file-storage'; +import { FileStorageObject } from '@filestorage/@lib/@types'; +import { Utils } from '@filestorage/@lib/@utils'; +import { IFileMapper } from '@filestorage/file/types'; +import { + UnifiedFilestorageFileInput, + UnifiedFilestorageFileOutput, +} from '@filestorage/file/types/model.unified'; +import { UnifiedFilestorageSharedlinkOutput } from '@filestorage/sharedlink/types/model.unified'; +import { Injectable } from '@nestjs/common'; +import { DropboxFileInput, DropboxFileOutput } from './types'; + +@Injectable() +export class DropboxFileMapper implements IFileMapper { + constructor( + private mappersRegistry: MappersRegistry, + private utils: Utils, + private coreUnificationService: CoreUnification, + ) { + this.mappersRegistry.registerService( + 'filestorage', + 'file', + 'dropbox', + this, + ); + } + + async desunify( + source: UnifiedFilestorageFileInput, + customFieldMappings?: { + slug: string; + remote_id: string; + }[], + ): Promise { + // todo: do something with customFieldMappings + return { + path: `/${source.name}`, + mode: 'add', + autorename: true, + }; + } + + async unify( + source: DropboxFileOutput | DropboxFileOutput[], + connectionId: string, + customFieldMappings?: { + slug: string; + remote_id: string; + }[], + ): Promise { + if (!Array.isArray(source)) { + return await this.mapSingleFileToUnified( + source, + connectionId, + customFieldMappings, + ); + } + // Handling array of DropboxFileOutput + return Promise.all( + source.map((file) => + this.mapSingleFileToUnified(file, connectionId, customFieldMappings), + ), + ); + } + + private async mapSingleFileToUnified( + file: DropboxFileOutput, + connectionId: string, + customFieldMappings?: { + slug: string; + remote_id: string; + }[], + ): Promise { + const result: UnifiedFilestorageFileOutput = { + remote_id: file.id, + remote_data: file, + name: file.name, + file_url: null, + mime_type: null, + size: file.size.toString(), + folder_id: null, + permission: null, + shared_link: null, + field_mappings: {}, + }; + + if (customFieldMappings) { + for (const mapping of customFieldMappings) { + result.field_mappings[mapping.slug] = file[mapping.remote_id]; + } + } + + return result; + } +} diff --git a/packages/api/src/filestorage/file/services/dropbox/types.ts b/packages/api/src/filestorage/file/services/dropbox/types.ts new file mode 100644 index 000000000..9cd5ac9c3 --- /dev/null +++ b/packages/api/src/filestorage/file/services/dropbox/types.ts @@ -0,0 +1,232 @@ +/** + * Represents a file-specific entry in the Dropbox API. + */ +export interface DropboxFileOutput { + /** + * A constant tag indicating the entry is a file. + * Value will always be `'file'`. + */ + '.tag': 'file'; + + /** + * The name of the file. + */ + name: string; + + /** + * The lowercased path of the file, useful for case-insensitive comparisons. + */ + path_lower: string; + + /** + * The display path of the file, with original casing. + */ + path_display: string; + + /** + * The Dropbox unique identifier for the file. + */ + id: string; + + /** + * The size of the file in bytes. + */ + size: number; + + /** + * A hash of the file content, useful for detecting file changes. + */ + content_hash: string; + + /** + * The revision number of the file. + * Useful for file versioning. + */ + rev: string; + + /** + * The timestamp of when the file was last modified on the client. + */ + client_modified: string; + + /** + * The timestamp of when the file was last modified on the Dropbox server. + */ + server_modified: string; + + /** + * Whether the file is downloadable. + */ + is_downloadable: boolean; + + /** + * Information about file sharing, such as if it is read-only or shared. + * This field is present if the file is part of a shared folder. + */ + sharing_info?: SharingInfo; + + /** + * Information about the export of the file, if it's an exportable file (e.g., Google Docs). + */ + export_info?: ExportInfo; + + /** + * The property groups associated with the file. + */ + property_groups?: PropertyGroup[]; + + /** + * Indicates whether the file has any explicit member policy. + */ + has_explicit_shared_members?: boolean; + + /** + * Information about file locking, if applicable. + */ + file_lock_info?: FileLockInfo; +} + +/** + * Represents sharing information for a file. + */ +export interface SharingInfo { + /** + * Whether the file is read-only for the current user. + */ + read_only: boolean; + + /** + * The ID of the parent shared folder, if this file is inside a shared folder. + */ + parent_shared_folder_id?: string; + + /** + * The unique ID of the shared folder. + */ + shared_folder_id?: string; + + /** + * Whether the file can be shared externally. + */ + traverse_only?: boolean; + + /** + * Whether the user has permission to manage sharing. + */ + no_access?: boolean; +} + +/** + * Represents export information for a file, if applicable. + */ +export interface ExportInfo { + /** + * The format to which the file can be exported (e.g., pdf, docx). + */ + export_as: string; +} + +/** + * Represents a property group associated with the file. + */ +export interface PropertyGroup { + /** + * The template ID of the property group. + */ + template_id: string; + + /** + * The list of properties under this group. + */ + fields: PropertyField[]; +} + +/** + * Represents a property field in a property group. + */ +export interface PropertyField { + /** + * The name of the property. + */ + name: string; + + /** + * The value of the property. + */ + value: string; +} +/** + * Represents a file lock information. + */ +export interface FileLockInfo { + /** + * The timestamp when the lock was created. + */ + created: string; + + /** + * Whether the user is the lockholder. + */ + is_lockholder: boolean; + + /** + * The name of the lockholder. + */ + lockholder_name: string; +} + +/** + * Represents the request body for uploading a new file in Dropbox. + */ +export interface DropboxFileInput { + /** + * The path in the user's Dropbox to save the file. + * Must match the pattern `(/(.|[\r\n])*)|(ns:[0-9]+(/.*)?)|(id:.*)?` + * Example: "/new_folder/myfile.txt" + */ + path: string; + + /** + * Selects what to do if the file already exists. + * The default for this union is "add". + * Options: "add", "overwrite", "update" + */ + mode: 'add' | 'overwrite' | 'update'; + + /** + * If true, Dropbox will automatically rename the file in case of a conflict. + * The default is false. + */ + autorename?: boolean; + + /** + * The value to store as the client_modified timestamp. + * Optional field in ISO 8601 format (e.g., "2024-09-12T14:00:00Z"). + */ + client_modified?: string; + + /** + * If true, suppresses user notifications about this file modification. + * The default is false. + */ + mute?: boolean; + + /** + * List of custom properties to add to the file. + * Optional field. + */ + property_groups?: PropertyGroup[]; + + /** + * If true, enforces stricter conflict detection. + * Defaults to false. + */ + strict_conflict?: boolean; + + /** + * A hash of the file content uploaded in this call. + * If provided, the uploaded content must match this hash. + * Optional field with length between 64 characters. + */ + content_hash?: string; +} diff --git a/packages/api/src/filestorage/folder/folder.module.ts b/packages/api/src/filestorage/folder/folder.module.ts index bd4c7cd9a..54920882c 100644 --- a/packages/api/src/filestorage/folder/folder.module.ts +++ b/packages/api/src/filestorage/folder/folder.module.ts @@ -1,3 +1,5 @@ +import { DropboxFolderMapper } from './services/dropbox/mappers'; +import { DropboxService } from './services/dropbox'; import { SharepointFolderMapper } from './services/sharepoint/mappers'; import { SharepointService } from './services/sharepoint'; import { BullQueueModule } from '@@core/@core-services/queues/queue.module'; @@ -35,6 +37,9 @@ import { SyncService } from './sync/sync.service'; SharepointService, SharepointFolderMapper, OnedriveService, + OnedriveFolderMapper, + DropboxService, + DropboxFolderMapper, GoogleDriveFolderService, ], exports: [SyncService], diff --git a/packages/api/src/filestorage/folder/services/dropbox/index.ts b/packages/api/src/filestorage/folder/services/dropbox/index.ts new file mode 100644 index 000000000..a3e60dc70 --- /dev/null +++ b/packages/api/src/filestorage/folder/services/dropbox/index.ts @@ -0,0 +1,129 @@ +import { Injectable } from '@nestjs/common'; +import { IFolderService } from '@filestorage/folder/types'; +import { FileStorageObject } from '@filestorage/@lib/@types'; +import axios from 'axios'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; +import { LoggerService } from '@@core/@core-services/logger/logger.service'; +import { ActionType, handle3rdPartyServiceError } from '@@core/utils/errors'; +import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; +import { ApiResponse } from '@@core/utils/types'; +import { ServiceRegistry } from '../registry.service'; +import { SyncParam } from '@@core/utils/types/interface'; +import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; +import { UnifiedFilestorageFileOutput } from '@filestorage/file/types/model.unified'; +import { DropboxFolderInput, DropboxFolderOutput } from './types'; +import { BoxFolderOutput } from '../box/types'; +// import { BoxFileOutput } from '@filestorage/file/services/box/types'; + +@Injectable() +export class DropboxService implements IFolderService { + constructor( + private prisma: PrismaService, + private logger: LoggerService, + private cryptoService: EncryptionService, + private registry: ServiceRegistry, + private ingestService: IngestDataService, + ) { + this.logger.setContext( + `${FileStorageObject.folder.toUpperCase()}:${DropboxService.name}`, + ); + this.registry.registerService('dropbox', this); + } + + async addFolder( + folderData: DropboxFolderInput, + linkedUserId: string, + ): Promise> { + try { + const connection = await this.prisma.connections.findFirst({ + where: { + id_linked_user: linkedUserId, + provider_slug: 'dropbox', + vertical: 'filestorage', + }, + }); + // ref: https://www.dropbox.com/developers/documentation/http/documentation#files-create_folder + const resp = await axios.post( + `${connection.account_url}/files/create_folder_v2`, + JSON.stringify(folderData), + { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${this.cryptoService.decrypt( + connection.access_token, + )}`, + }, + }, + ); + return { + data: resp?.data, + message: 'Dropbox folder created', + statusCode: 201, + }; + } catch (error) { + console.log(error.response); + throw error; + } + } + + async getAllFolders(connection: any): Promise { + // ref: https://www.dropbox.com/developers/documentation/http/documentation#files-list_folder + const folders: DropboxFolderOutput[] = []; + let cursor: string | null = null; + let hasMore = true; + + while (hasMore) { + const url = cursor + ? `${connection.account_url}/files/list_folder/continue` + : `${connection.account_url}/files/list_folder`; + const data = cursor ? { cursor } : { path: '', recursive: true }; + + const response = await axios.post(url, data, { + headers: { + Authorization: `Bearer ${this.cryptoService.decrypt( + connection.access_token, + )}`, + 'Content-Type': 'application/json', + }, + }); + + const { entries, has_more, cursor: newCursor } = response.data; + + // Collect all folder entries + folders.push( + ...entries.filter((entry: any) => entry['.tag'] === 'folder'), + ); + + hasMore = has_more; + cursor = newCursor; + } + + return folders; + } + + async sync(data: SyncParam): Promise> { + try { + const { linkedUserId } = data; + + const connection = await this.prisma.connections.findFirst({ + where: { + id_linked_user: linkedUserId, + provider_slug: 'dropbox', + vertical: 'filestorage', + }, + }); + + const results = await this.getAllFolders(connection); + this.logger.log(`Synced dropbox folders !`); + + return { + data: results, + message: 'Dropbox folders retrieved', + statusCode: 200, + }; + } catch (error) { + console.log(error.response); + throw error; + } + } +} diff --git a/packages/api/src/filestorage/folder/services/dropbox/mappers.ts b/packages/api/src/filestorage/folder/services/dropbox/mappers.ts new file mode 100644 index 000000000..0928a71a6 --- /dev/null +++ b/packages/api/src/filestorage/folder/services/dropbox/mappers.ts @@ -0,0 +1,110 @@ +import { MappersRegistry } from '@@core/@core-services/registries/mappers.registry'; +import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; +import { OriginalSharedLinkOutput } from '@@core/utils/types/original/original.file-storage'; +import { Utils } from '@filestorage/@lib/@utils'; +import { IFolderMapper } from '@filestorage/folder/types'; +import { + UnifiedFilestorageFolderInput, + UnifiedFilestorageFolderOutput, +} from '@filestorage/folder/types/model.unified'; +import { UnifiedFilestorageSharedlinkOutput } from '@filestorage/sharedlink/types/model.unified'; +import { Injectable } from '@nestjs/common'; +import { FileStorageObject } from '@filestorage/@lib/@types'; +import { DropboxFolderInput, DropboxFolderOutput } from './types'; + +@Injectable() +export class DropboxFolderMapper implements IFolderMapper { + constructor( + private mappersRegistry: MappersRegistry, + private utils: Utils, + private coreUnificationService: CoreUnification, + ) { + this.mappersRegistry.registerService( + 'filestorage', + 'folder', + 'dropbox', + this, + ); + } + + async desunify( + source: UnifiedFilestorageFolderInput, + customFieldMappings?: { + slug: string; + remote_id: string; + }[], + ): Promise { + const result: DropboxFolderInput = { + path: `/${source.name}`, + autorename: true, + }; + + if (customFieldMappings && source.field_mappings) { + for (const [k, v] of Object.entries(source.field_mappings)) { + const mapping = customFieldMappings.find( + (mapping) => mapping.slug === k, + ); + if (mapping) { + result[mapping.remote_id] = v; + } + } + } + + return result; + } + + async unify( + source: DropboxFolderOutput | DropboxFolderOutput[], + connectionId: string, + customFieldMappings?: { + slug: string; + remote_id: string; + }[], + ): Promise< + UnifiedFilestorageFolderOutput | UnifiedFilestorageFolderOutput[] + > { + if (!Array.isArray(source)) { + return await this.mapSingleFolderToUnified( + source, + connectionId, + customFieldMappings, + ); + } else { + return await Promise.all( + source.map((s) => + this.mapSingleFolderToUnified(s, connectionId, customFieldMappings), + ), + ); + } + } + + private async mapSingleFolderToUnified( + folder: DropboxFolderOutput, + connectionId: string, + customFieldMappings?: { + slug: string; + remote_id: string; + }[], + ): Promise { + const result: UnifiedFilestorageFolderOutput = { + remote_id: folder.id, + remote_data: folder, + name: folder.name, + size: null, + folder_url: null, + description: null, + drive_id: null, + parent_folder_id: null, + shared_link: null, + permission: null, + field_mappings: {}, + }; + + if (customFieldMappings) { + for (const mapping of customFieldMappings) { + result.field_mappings[mapping.slug] = folder[mapping.remote_id]; + } + } + return result; + } +} diff --git a/packages/api/src/filestorage/folder/services/dropbox/types.ts b/packages/api/src/filestorage/folder/services/dropbox/types.ts new file mode 100644 index 000000000..7e6309562 --- /dev/null +++ b/packages/api/src/filestorage/folder/services/dropbox/types.ts @@ -0,0 +1,73 @@ +/** + * Represents a folder-specific entry in the Dropbox API. + */ +export interface DropboxFolderOutput { + /** + * A constant tag indicating the entry is a folder. + * Value will always be `'folder'`. + */ + '.tag': 'folder'; + + /** + * The name of the folder. + */ + name: string; + + /** + * The lowercased path of the folder, useful for case-insensitive comparisons. + */ + path_lower: string; + + /** + * The display path of the folder, with original casing. + */ + path_display: string; + + /** + * The Dropbox unique identifier for the folder. + */ + id: string; + + /** + * Information about folder sharing, such as if it is read-only or shared. + * This field is present if the folder is part of a shared folder. + */ + sharing_info?: SharingInfo; +} + +/** + * Represents sharing information for a folder. + */ +export interface SharingInfo { + /** + * Whether the folder is read-only for the current user. + */ + read_only: boolean; + + /** + * The ID of the parent shared folder, if this folder is inside a shared folder. + */ + parent_shared_folder_id?: string; + + /** + * The unique ID of the shared folder. + */ + shared_folder_id?: string; +} + +/** + * Represents the request body for creating a new folder in Dropbox. + */ +export interface DropboxFolderInput { + /** + * The path to the folder you want to create, including the new folder's name. + * Example: "/new_folder_name" + */ + path: string; + + /** + * If true, the folder will be automatically renamed if a conflict occurs (i.e., if a folder with the same name already exists). + * Defaults to false. + */ + autorename?: boolean; +} diff --git a/packages/api/src/filestorage/group/group.module.ts b/packages/api/src/filestorage/group/group.module.ts index 78c5ed835..f911b3d26 100644 --- a/packages/api/src/filestorage/group/group.module.ts +++ b/packages/api/src/filestorage/group/group.module.ts @@ -1,3 +1,5 @@ +import { DropboxGroupMapper } from './services/dropbox/mappers'; +import { DropboxService } from './services/dropbox'; import { SharepointGroupMapper } from './services/sharepoint/mappers'; import { SharepointService } from './services/sharepoint'; import { OnedriveGroupMapper } from './services/onedrive/mappers'; @@ -32,6 +34,8 @@ import { SyncService } from './sync/sync.service'; SharepointGroupMapper, OnedriveService, OnedriveGroupMapper, + DropboxService, + DropboxGroupMapper, ], exports: [SyncService], }) diff --git a/packages/api/src/filestorage/group/services/box/mappers.ts b/packages/api/src/filestorage/group/services/box/mappers.ts index 51ffb0b9f..457e2b818 100644 --- a/packages/api/src/filestorage/group/services/box/mappers.ts +++ b/packages/api/src/filestorage/group/services/box/mappers.ts @@ -64,7 +64,7 @@ export class BoxGroupMapper implements IGroupMapper { return { remote_id: group.id, name: group.name || null, - users: null, + users: [], remote_was_deleted: null, //created_at: group.created_at || null, //modified_at: group.modified_at || null, diff --git a/packages/api/src/filestorage/group/services/dropbox/index.ts b/packages/api/src/filestorage/group/services/dropbox/index.ts new file mode 100644 index 000000000..4ebc75247 --- /dev/null +++ b/packages/api/src/filestorage/group/services/dropbox/index.ts @@ -0,0 +1,62 @@ +import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; +import { LoggerService } from '@@core/@core-services/logger/logger.service'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; +import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; +import { FileStorageObject } from '@filestorage/@lib/@types'; +import { IGroupService } from '@filestorage/group/types'; +import { Injectable } from '@nestjs/common'; +import axios from 'axios'; +import { ServiceRegistry } from '../registry.service'; +import { DropboxGroupOutput } from './types'; + +@Injectable() +export class DropboxService implements IGroupService { + constructor( + private prisma: PrismaService, + private logger: LoggerService, + private cryptoService: EncryptionService, + private registry: ServiceRegistry, + ) { + this.logger.setContext( + `${FileStorageObject.group.toUpperCase()}:${DropboxService.name}`, + ); + this.registry.registerService('dropbox', this); + } + + async sync(data: SyncParam): Promise> { + try { + const { linkedUserId } = data; + const connection = await this.prisma.connections.findFirst({ + where: { + id_linked_user: linkedUserId, + provider_slug: 'dropbox', + vertical: 'filestorage', + }, + }); + + // ref: https://www.dropbox.com/developers/documentation/http/teams#team-groups-list + const resp = await axios.post( + `${connection.account_url}/team/groups/list`, + { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${this.cryptoService.decrypt( + connection.access_token, + )}`, + }, + }, + ); + + this.logger.log(`Synced dropbox groups !`); + + return { + data: resp.data.groups, + message: 'Dropbox groups retrieved', + statusCode: 200, + }; + } catch (error) { + throw error; + } + } +} diff --git a/packages/api/src/filestorage/group/services/dropbox/mappers.ts b/packages/api/src/filestorage/group/services/dropbox/mappers.ts new file mode 100644 index 000000000..3bbc9b241 --- /dev/null +++ b/packages/api/src/filestorage/group/services/dropbox/mappers.ts @@ -0,0 +1,78 @@ +import { + UnifiedFilestorageGroupInput, + UnifiedFilestorageGroupOutput, +} from '@filestorage/group/types/model.unified'; +import { IGroupMapper } from '@filestorage/group/types'; +import { Utils } from '@filestorage/@lib/@utils'; +import { MappersRegistry } from '@@core/@core-services/registries/mappers.registry'; +import { Injectable } from '@nestjs/common'; +import { DropboxGroupInput, DropboxGroupOutput } from './types'; + +@Injectable() +export class DropboxGroupMapper implements IGroupMapper { + constructor(private mappersRegistry: MappersRegistry, private utils: Utils) { + this.mappersRegistry.registerService( + 'filestorage', + 'group', + 'dropbox', + this, + ); + } + + async desunify( + source: UnifiedFilestorageGroupInput, + customFieldMappings?: { + slug: string; + remote_id: string; + }[], + ): Promise { + return; + } + + async unify( + source: DropboxGroupOutput | DropboxGroupOutput[], + connectionId: string, + customFieldMappings?: { + slug: string; + remote_id: string; + }[], + ): Promise { + if (!Array.isArray(source)) { + return await this.mapSingleGroupToUnified( + source, + connectionId, + customFieldMappings, + ); + } + // Handling array of DropboxGroupOutput + return Promise.all( + source.map((group) => + this.mapSingleGroupToUnified(group, connectionId, customFieldMappings), + ), + ); + } + + private async mapSingleGroupToUnified( + group: DropboxGroupOutput, + connectionId: string, + customFieldMappings?: { + slug: string; + remote_id: string; + }[], + ): Promise { + const field_mappings: { [key: string]: any } = {}; + if (customFieldMappings) { + for (const mapping of customFieldMappings) { + field_mappings[mapping.slug] = group[mapping.remote_id]; + } + } + return { + remote_id: group.group_id, + remote_data: group, + name: group.group_name, + users: [], + field_mappings, + remote_was_deleted: null, + }; + } +} diff --git a/packages/api/src/filestorage/group/services/dropbox/types.ts b/packages/api/src/filestorage/group/services/dropbox/types.ts new file mode 100644 index 000000000..7d5707948 --- /dev/null +++ b/packages/api/src/filestorage/group/services/dropbox/types.ts @@ -0,0 +1,73 @@ +/** + * Represents a group in Dropbox. + */ +export interface DropboxGroupOutput { + /** + * The name of the group. + */ + group_name: string; + + /** + * The unique identifier for the group. + */ + group_id: string; + + /** + * The management type of the group. + * This field indicates who is allowed to manage the group. + */ + group_management_type: { + '.tag': GroupManagementType; + }; + + /** + * An external ID associated with the group. + * This field is optional and allows an admin to attach an arbitrary ID to the group. + */ + group_external_id?: string; + + /** + * The number of members in the group. + * This field is optional. + */ + member_count?: number; +} + +/** + * Represents the type of management for a group in Dropbox. + * This determines who is allowed to manage the group. + */ +type GroupManagementType = + | 'user_managed' + | 'company_managed' + | 'system_managed'; + +/** + * Represents the input data for creating or updating a group in Dropbox. + */ +export interface DropboxGroupInput { + /** + * The name of the group. + */ + group_name: string; + + /** + * Whether to automatically add the creator of the group as an owner. + * The default value is `false`. + */ + add_creator_as_owner?: boolean; + + /** + * An external ID associated with the group. + * This field allows the creator of a team to attach an arbitrary external ID to the group. + * This field is optional. + */ + group_external_id?: string; + + /** + * The management type of the group. + * Determines whether the group can be managed by selected users or only by team admins. + * This field is optional. + */ + group_management_type?: GroupManagementType; +} diff --git a/packages/api/src/filestorage/user/services/dropbox/index.ts b/packages/api/src/filestorage/user/services/dropbox/index.ts new file mode 100644 index 000000000..1d3d19f19 --- /dev/null +++ b/packages/api/src/filestorage/user/services/dropbox/index.ts @@ -0,0 +1,63 @@ +import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; +import { LoggerService } from '@@core/@core-services/logger/logger.service'; +import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; +import { ApiResponse } from '@@core/utils/types'; +import { SyncParam } from '@@core/utils/types/interface'; +import { FileStorageObject } from '@filestorage/@lib/@types'; +import { IUserService } from '@filestorage/user/types'; +import { Injectable } from '@nestjs/common'; +import axios from 'axios'; +import { ServiceRegistry } from '../registry.service'; +import { DropboxUserOutput } from './types'; + +@Injectable() +export class DropboxService implements IUserService { + constructor( + private prisma: PrismaService, + private logger: LoggerService, + private cryptoService: EncryptionService, + private registry: ServiceRegistry, + ) { + this.logger.setContext( + `${FileStorageObject.user.toUpperCase()}:${DropboxService.name}`, + ); + this.registry.registerService('dropbox', this); + } + + async sync(data: SyncParam): Promise> { + try { + const { linkedUserId } = data; + + const connection = await this.prisma.connections.findFirst({ + where: { + id_linked_user: linkedUserId, + provider_slug: 'dropbox', + vertical: 'filestorage', + }, + }); + + // ref: https://www.dropbox.com/developers/documentation/http/teams#team-members-list + const resp = await axios.post( + `${connection.account_url}/team/members/list_2`, + { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${this.cryptoService.decrypt( + connection.access_token, + )}`, + }, + }, + ); + + this.logger.log(`Synced dropbox users !`); + + return { + data: resp.data.members as DropboxUserOutput[], + message: 'Dropbox users retrieved', + statusCode: 200, + }; + } catch (error) { + throw error; + } + } +} diff --git a/packages/api/src/filestorage/user/services/dropbox/mappers.ts b/packages/api/src/filestorage/user/services/dropbox/mappers.ts new file mode 100644 index 000000000..79203f617 --- /dev/null +++ b/packages/api/src/filestorage/user/services/dropbox/mappers.ts @@ -0,0 +1,82 @@ +import { + UnifiedFilestorageUserInput, + UnifiedFilestorageUserOutput, +} from '@filestorage/user/types/model.unified'; +import { IUserMapper } from '@filestorage/user/types'; +import { Utils } from '@filestorage/@lib/@utils'; +import { MappersRegistry } from '@@core/@core-services/registries/mappers.registry'; +import { Injectable } from '@nestjs/common'; +import { DropboxUserInput, DropboxUserOutput } from './types'; + +@Injectable() +export class DropboxUserMapper implements IUserMapper { + constructor(private mappersRegistry: MappersRegistry, private utils: Utils) { + this.mappersRegistry.registerService( + 'filestorage', + 'user', + 'dropbox', + this, + ); + } + + async desunify( + source: UnifiedFilestorageUserInput, + customFieldMappings?: { + slug: string; + remote_id: string; + }[], + ): Promise { + return; + } + + async unify( + source: DropboxUserOutput | DropboxUserOutput[], + connectionId: string, + customFieldMappings?: { + slug: string; + remote_id: string; + }[], + ): Promise { + if (!Array.isArray(source)) { + return await this.mapSingleUserToUnified( + source, + connectionId, + customFieldMappings, + ); + } + // Handling array of DropboxUserOutput + return Promise.all( + source.map((user) => + this.mapSingleUserToUnified(user, connectionId, customFieldMappings), + ), + ); + } + + private async mapSingleUserToUnified( + user: DropboxUserOutput, + connectionId: string, + customFieldMappings?: { + slug: string; + remote_id: string; + }[], + ): Promise { + const field_mappings: { [key: string]: any } = {}; + if (customFieldMappings) { + for (const mapping of customFieldMappings) { + if (user.profile.hasOwnProperty(mapping.slug)) { + field_mappings[mapping.remote_id] = + user.profile[mapping.slug] || null; + } + } + } + + return { + remote_id: user.profile.account_id, + remote_data: user, + name: user.profile.name?.display_name || null, + email: user.profile.email, + is_me: false, + field_mappings, + }; + } +} diff --git a/packages/api/src/filestorage/user/services/dropbox/types.ts b/packages/api/src/filestorage/user/services/dropbox/types.ts new file mode 100644 index 000000000..aa83a502b --- /dev/null +++ b/packages/api/src/filestorage/user/services/dropbox/types.ts @@ -0,0 +1,228 @@ +/** + * Represents the profile information for a Dropbox team member. + */ +export interface DropboxUserProfile { + /** + * The unique identifier for the member within Dropbox as a team member. + */ + team_member_id: string; + + /** + * The email address of the member. + */ + email: string; + + /** + * Whether the email address has been verified. + */ + email_verified: boolean; + + /** + * The status of the member. + */ + status: { + /** + * The status of the member. + * Possible values: 'active', 'inactive', 'suspended', 'invited', etc. + */ + '.tag': 'active' | 'inactive' | 'suspended' | 'invited'; + }; + + /** + * The name details of the member. + */ + name: { + /** + * The abbreviated name of the member. + */ + abbreviated_name?: string; + + /** + * The display name of the member. + */ + display_name?: string; + + /** + * The familiar name of the member. + */ + familiar_name?: string; + + /** + * The given name of the member. + */ + given_name?: string; + + /** + * The surname of the member. + */ + surname?: string; + }; + + /** + * The membership type of the member. + */ + membership_type: { + /** + * The type of membership. + * Possible values: 'full' (normal team member), 'limited' (does not use a license), etc. + */ + '.tag': 'full' | 'limited'; + }; + + /** + * List of group IDs that the member belongs to. + */ + groups?: string[]; + + /** + * The namespace ID of the member's folder. + */ + member_folder_id: string; + + /** + * The namespace ID of the member's root folder. + */ + root_folder_id: string; + + /** + * An external ID that a team can attach to the member. + */ + external_id?: string; + + /** + * A user's account identifier. + */ + account_id?: string; + + /** + * List of secondary email addresses for the member. + */ + secondary_emails?: { + /** + * A secondary email address. + */ + email: string; + + /** + * Whether the secondary email address has been verified. + */ + is_verified: boolean; + }[]; + + /** + * The date and time the member was invited to the team. + */ + invited_on?: string; // ISO 8601 timestamp + + /** + * The date and time the member joined the team. + */ + joined_on?: string; // ISO 8601 timestamp + + /** + * The date and time the member was suspended from the team. + */ + suspended_on?: string; // ISO 8601 timestamp + + /** + * A unique ID used for SAML authentication. + */ + persistent_id?: string; + + /** + * Whether the member is a directory restricted user. + */ + is_directory_restricted?: boolean; + + /** + * URL for the photo representing the member. + */ + profile_photo_url?: string; +} + +/** + * Represents a role assigned to a Dropbox team member. + */ +export interface DropboxUserRole { + /** + * The unique ID of the role. + */ + role_id: string; + + /** + * The display name of the role. + */ + name: string; + + /** + * Description of the role and its permissions. + */ + description: string; +} + +/** + * Represents a Dropbox team member entry returned from the /team/members/list_v2 API. + */ +export interface DropboxUserOutput { + /** + * The profile information of the team member. + */ + profile: DropboxUserProfile; + + /** + * List of roles assigned to the team member. + */ + roles?: DropboxUserRole[]; +} + +/** + * Represents the input information for adding a new team member in Dropbox. + */ +export interface DropboxUserInput { + /** + * The email address of the member to be added. + */ + member_email: string; + + /** + * The given (first) name of the member. + * This field is optional. + */ + member_given_name?: string; + + /** + * The surname (last name) of the member. + * This field is optional. + */ + member_surname?: string; + + /** + * An external ID to associate with the member. + * This field is optional. + */ + member_external_id?: string; + + /** + * A persistent ID for the member, used for SAML authentication. + * This field is optional and only available for teams using persistent ID SAML configuration. + */ + member_persistent_id?: string; + + /** + * Whether to send a welcome email to the member. + * If set to false, no email invitation will be sent to the user. Defaults to true. + */ + send_welcome_email?: boolean; + + /** + * Whether the user is directory restricted. + * This field is optional. + */ + is_directory_restricted?: boolean; + + /** + * List of role IDs to assign to the member. + * This field is optional and can have a maximum of one role ID. + */ + role_ids?: string[]; +} diff --git a/packages/api/src/filestorage/user/user.module.ts b/packages/api/src/filestorage/user/user.module.ts index ae85928e2..4c53c8dbf 100644 --- a/packages/api/src/filestorage/user/user.module.ts +++ b/packages/api/src/filestorage/user/user.module.ts @@ -1,3 +1,5 @@ +import { DropboxUserMapper } from './services/dropbox/mappers'; +import { DropboxService } from './services/dropbox'; import { SharepointUserMapper } from './services/sharepoint/mappers'; import { SharepointService } from './services/sharepoint'; import { OnedriveUserMapper } from './services/onedrive/mappers'; @@ -31,6 +33,8 @@ import { BullQueueModule } from '@@core/@core-services/queues/queue.module'; SharepointUserMapper, OnedriveService, OnedriveUserMapper, + DropboxService, + DropboxUserMapper, ], exports: [SyncService], }) diff --git a/packages/shared/src/connectors/enum.ts b/packages/shared/src/connectors/enum.ts index 8d2dd6256..9aab0525a 100644 --- a/packages/shared/src/connectors/enum.ts +++ b/packages/shared/src/connectors/enum.ts @@ -26,7 +26,10 @@ export enum TicketingConnectors { } export enum FilestorageConnectors { - BOX = 'box', - SHAREPOINT = 'sharepoint', - ONEDRIVE = 'onedrive', + BOX = 'box', + DROPBOX = 'dropbox', + ONEDRIVE = 'onedrive' + SHAREPOINT = 'sharepoint', + ONEDRIVE = 'onedrive', + GOOGLEDRIVE = 'googledrive' } diff --git a/packages/shared/src/connectors/index.ts b/packages/shared/src/connectors/index.ts index 1d2a5b16c..92b7df549 100644 --- a/packages/shared/src/connectors/index.ts +++ b/packages/shared/src/connectors/index.ts @@ -4,5 +4,5 @@ export const ATS_PROVIDERS = ['ashby']; export const ACCOUNTING_PROVIDERS = []; export const TICKETING_PROVIDERS = ['zendesk', 'front', 'jira', 'gorgias', 'gitlab', 'github', 'linear']; export const MARKETINGAUTOMATION_PROVIDERS = []; -export const FILESTORAGE_PROVIDERS = ['box', 'onedrive', 'sharepoint']; +export const FILESTORAGE_PROVIDERS = ['box', 'onedrive', 'sharepoint', 'dropbox', 'googledrive']; export const ECOMMERCE_PROVIDERS = ['shopify', 'woocommerce', 'squarespace', 'amazon', 'webflow']; diff --git a/packages/shared/src/connectors/metadata.ts b/packages/shared/src/connectors/metadata.ts index 646a59bb1..1d1f97396 100644 --- a/packages/shared/src/connectors/metadata.ts +++ b/packages/shared/src/connectors/metadata.ts @@ -2732,14 +2732,15 @@ export const CONNECTORS_METADATA: ProvidersConfig = { } }, 'dropbox': { + scopes: 'files.metadata.read files.metadata.write files.content.read files.content.write team_data.member members.read groups.read' , urls: { docsUrl: 'https://www.dropbox.com/developers/documentation/http/documentation', - apiUrl: 'https://api.dropboxapi.com', + apiUrl: 'https://api.dropboxapi.com/2', authBaseUrl: 'https://www.dropbox.com/oauth2/authorize' }, logoPath: 'https://cdn2.iconfinder.com/data/icons/metro-ui-dock/512/Dropbox.png', description: 'Sync & Create contacts, deals, companies, notes, engagements, stages, tasks and users', - active: false, + active: true, authStrategy: { strategy: AuthStrategy.oauth2 }