Spaces:
Runtime error
Runtime error
| import { Injectable, Logger } from '@nestjs/common'; | |
| import { ConfigService } from '@nestjs/config'; | |
| import { Dropbox } from 'dropbox'; | |
| () | |
| export class DropboxStorageService { | |
| private readonly logger = new Logger(DropboxStorageService.name); | |
| private dbx: Dropbox; | |
| private appFolder: string; | |
| constructor(private configService: ConfigService) { | |
| const accessToken = this.configService.get<string>( | |
| 'dropboxStorage.accessToken', | |
| ); | |
| this.appFolder = | |
| this.configService.get<string>('dropboxStorage.appFolder') || | |
| '/StreamflixDev'; | |
| if (!accessToken) { | |
| throw new Error('DROPBOX_ACCESS_TOKEN is not configured'); | |
| } | |
| this.dbx = new Dropbox({ accessToken }); | |
| this.logger.log('Dropbox Storage Service initialized'); | |
| } | |
| /** | |
| * Get folder path based on file type | |
| */ | |
| private getFolderPath(fileType: string): string { | |
| const folderMap = { | |
| IMAGE: 'images', | |
| VIDEO: 'videos', | |
| DOCUMENT: 'documents', | |
| }; | |
| return folderMap[fileType] || 'others'; | |
| } | |
| /** | |
| * Upload file to Dropbox | |
| */ | |
| async uploadFile(params: { | |
| fileBuffer: Buffer; | |
| filename: string; | |
| fileType?: string; | |
| customFilename?: string; | |
| }) { | |
| const { fileBuffer, filename, fileType, customFilename } = params; | |
| const subfolder = this.getFolderPath(fileType || 'IMAGE'); | |
| const timestamp = Date.now(); | |
| // Use custom filename if provided, otherwise use original filename | |
| const finalFilename = customFilename || filename; | |
| const sanitizedFilename = finalFilename.replace(/[^a-zA-Z0-9.-]/g, '_'); | |
| const dropboxPath = `${this.appFolder}/${subfolder}/${timestamp}-${sanitizedFilename}`; | |
| try { | |
| const res = await this.dbx.filesUpload({ | |
| path: dropboxPath, | |
| contents: fileBuffer, | |
| mode: { '.tag': 'add' }, | |
| autorename: true, | |
| mute: false, | |
| strict_conflict: false, | |
| }); | |
| this.logger.log(`File uploaded: ${res.result.path_lower}`); | |
| // Create shared link | |
| const sharedUrl = await this.createSharedLink( | |
| res.result.path_lower || dropboxPath, | |
| ); | |
| const directUrl = this.toDirectUrl(sharedUrl); | |
| return { | |
| success: true, | |
| name: res.result.name, | |
| dropboxPath: res.result.path_lower, | |
| size: (res.result as any).size, | |
| publicUrl: directUrl, | |
| sharedUrl: sharedUrl, | |
| }; | |
| } catch (error) { | |
| this.logger.error('Upload failed:', error); | |
| throw error; | |
| } | |
| } | |
| /** | |
| * Create shared link for a file | |
| */ | |
| async createSharedLink(path: string): Promise<string> { | |
| try { | |
| const res = await this.dbx.sharingCreateSharedLinkWithSettings({ path }); | |
| return res.result.url; | |
| } catch (err: any) { | |
| // Handle case where link already exists | |
| if ( | |
| err?.error?.error?.['.tag'] === 'shared_link_already_exists' || | |
| err?.error?.error_summary?.includes('shared_link_already_exists') | |
| ) { | |
| const list = await this.dbx.sharingListSharedLinks({ path }); | |
| if (list.result.links && list.result.links.length > 0) { | |
| return list.result.links[0].url; | |
| } | |
| } | |
| throw err; | |
| } | |
| } | |
| /** | |
| * Convert Dropbox preview URL to direct download URL | |
| */ | |
| private toDirectUrl(sharedUrl: string): string { | |
| // Handle new Dropbox shared link format (scl/fi/) | |
| if (sharedUrl.includes('dropbox.com/scl/fi/')) { | |
| // Replace dl=0 with raw=1 for direct access | |
| return sharedUrl.replace('dl=0', 'raw=1'); | |
| } | |
| // Handle old format | |
| if (sharedUrl.includes('?dl=0')) { | |
| return sharedUrl.replace('?dl=0', '?raw=1'); | |
| } | |
| if (!sharedUrl.includes('?')) { | |
| return `${sharedUrl}?raw=1`; | |
| } | |
| return sharedUrl; | |
| } | |
| /** | |
| * Get file metadata and shared link | |
| */ | |
| async getFileInfo(path: string) { | |
| try { | |
| const metadata = await this.dbx.filesGetMetadata({ path }); | |
| const sharedUrl = await this.createSharedLink(path); | |
| const directUrl = this.toDirectUrl(sharedUrl); | |
| return { | |
| success: true, | |
| name: metadata.result.name, | |
| path: metadata.result.path_lower, | |
| size: (metadata.result as any).size, | |
| publicUrl: directUrl, | |
| sharedUrl: sharedUrl, | |
| }; | |
| } catch (error) { | |
| this.logger.error('Get file info failed:', error); | |
| throw error; | |
| } | |
| } | |
| /** | |
| * Delete file from Dropbox | |
| */ | |
| async deleteFile(path: string) { | |
| try { | |
| const res = await this.dbx.filesDeleteV2({ path }); | |
| this.logger.log(`File deleted: ${path}`); | |
| return { | |
| success: true, | |
| deletedPath: path, | |
| metadata: res.result.metadata, | |
| }; | |
| } catch (error) { | |
| this.logger.error('Delete failed:', error); | |
| throw error; | |
| } | |
| } | |
| /** | |
| * Delete file by public URL | |
| */ | |
| async deleteFileByUrl(url: string) { | |
| const path = this.extractPathFromUrl(url); | |
| if (!path) { | |
| throw new Error('Invalid Dropbox URL'); | |
| } | |
| return this.deleteFile(path); | |
| } | |
| /** | |
| * Extract Dropbox path from URL | |
| */ | |
| private extractPathFromUrl(url: string): string | null { | |
| try { | |
| // Handle direct URLs like: https://www.dropbox.com/s/xxxxx/filename.jpg?raw=1 | |
| // or shared URLs: https://www.dropbox.com/s/xxxxx/filename.jpg?dl=0 | |
| const urlObj = new URL(url); | |
| const pathname = urlObj.pathname; | |
| // Extract filename from URL | |
| const parts = pathname.split('/'); | |
| const filename = parts[parts.length - 1]; | |
| if (!filename) { | |
| return null; | |
| } | |
| // Try to match against app folder structure | |
| // This is a simplified approach - you might need to store the full path in DB | |
| return `${this.appFolder}/images/${filename}`; | |
| } catch (error) { | |
| this.logger.error('Failed to extract path from URL:', error); | |
| return null; | |
| } | |
| } | |
| /** | |
| * List files in a folder | |
| */ | |
| async listFiles(folderPath?: string) { | |
| const path = folderPath || this.appFolder; | |
| try { | |
| const res = await this.dbx.filesListFolder({ path }); | |
| return { | |
| success: true, | |
| files: res.result.entries, | |
| hasMore: res.result.has_more, | |
| }; | |
| } catch (error) { | |
| this.logger.error('List files failed:', error); | |
| throw error; | |
| } | |
| } | |
| } | |