Skip to content

Commit

Permalink
Merge pull request #695 from amuwal/dropbox-integration
Browse files Browse the repository at this point in the history
feat: Add integration with: Dropbox
  • Loading branch information
naelob authored Sep 13, 2024
2 parents e73ef8e + 74baa59 commit cd1b728
Show file tree
Hide file tree
Showing 24 changed files with 1,422 additions and 17 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
3 changes: 3 additions & 0 deletions packages/api/scripts/init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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 )
);

Expand Down
9 changes: 5 additions & 4 deletions packages/api/scripts/seed.sql
Original file line number Diff line number Diff line change
@@ -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','[email protected]', '$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'),
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -99,16 +112,22 @@ 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;

/* folder */
export type OriginalFolderInput =
| BoxFolderInput
| OnedriveFolderInput
| SharepointFolderInput;
| DropboxFolderInput;
| SharepointFolderInput
| GoogleDriveFolderInput;

Expand All @@ -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
Expand All @@ -148,16 +171,22 @@ export type FileStorageObjectInput =
/* OUTPUT */

/* file */

/* folder */
export type OriginalFileOutput =
| BoxFileOutput
| OnedriveFileOutput
| SharepointFileOutput;
| DropboxFileOutput;
| SharepointFileOutput
| GoogleDriveFileOutput;

/* folder */
export type OriginalFolderOutput =
| BoxFolderOutput
| OnedriveFolderOutput
| SharepointFolderOutput;
| DropboxFolderOutput;
| SharepointFolderOutput
| GoogleDriveFolderOutput;

Expand All @@ -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
Expand Down
12 changes: 10 additions & 2 deletions packages/api/src/filestorage/file/file.module.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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';
Expand All @@ -33,6 +38,9 @@ import { SyncService } from './sync/sync.service';
SharepointService,
SharepointFileMapper,
OnedriveService,
OnedriveFileMapper,
DropboxService,
DropboxFileMapper,
GoogleDriveService,
],
exports: [SyncService, ServiceRegistry],
Expand Down
113 changes: 113 additions & 0 deletions packages/api/src/filestorage/file/services/dropbox/index.ts
Original file line number Diff line number Diff line change
@@ -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<DropboxFileOutput[]> {
// 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<ApiResponse<DropboxFileOutput[]>> {
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;
}
}
}
97 changes: 97 additions & 0 deletions packages/api/src/filestorage/file/services/dropbox/mappers.ts
Original file line number Diff line number Diff line change
@@ -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<DropboxFileInput> {
// 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<UnifiedFilestorageFileOutput | UnifiedFilestorageFileOutput[]> {
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<UnifiedFilestorageFileOutput> {
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;
}
}
Loading

0 comments on commit cd1b728

Please sign in to comment.