Spaces:
Runtime error
Runtime error
| import { Injectable, Logger, NotFoundException } from '@nestjs/common'; | |
| import { ConfigService } from '@nestjs/config'; | |
| import ImageKit from 'imagekit'; | |
| () | |
| export class ImagekitioService { | |
| private readonly logger = new Logger(ImagekitioService.name); | |
| private imagekit: ImageKit; | |
| constructor(private configService: ConfigService) { | |
| this.imagekit = new ImageKit({ | |
| publicKey: this.configService.get<string>('imagekitio.publicKey') || '', | |
| privateKey: this.configService.get<string>('imagekitio.privateKey') || '', | |
| urlEndpoint: | |
| this.configService.get<string>('imagekitio.urlEndpoint') || '', | |
| }); | |
| } | |
| /** | |
| * Get folder path based on file type | |
| * @param fileType - The file type (IMAGE, DOCUMENT, VIDEO) | |
| * @returns Folder path for the file type | |
| */ | |
| private getFolderPath(fileType: string): string { | |
| const folderMap: Record<string, string> = { | |
| IMAGE: 'images', | |
| DOCUMENT: 'documents', | |
| VIDEO: 'videos', | |
| }; | |
| return folderMap[fileType.toUpperCase()] || 'others'; | |
| } | |
| /** | |
| * Get authentication parameters for client-side upload | |
| * @param fileType - Optional file type restriction (IMAGE, DOCUMENT, VIDEO) | |
| * @returns Authentication parameters (token, expire, signature, folder) | |
| */ | |
| getAuthenticationParameters(fileType?: string) { | |
| try { | |
| // Determine folder based on file type | |
| const folder = fileType ? this.getFolderPath(fileType) : 'others'; | |
| // Generate auth params | |
| const authParams = this.imagekit.getAuthenticationParameters(); | |
| this.logger.log( | |
| `Generated authentication parameters for ImageKit upload${fileType ? ` (type: ${fileType}, folder: ${folder})` : ''}`, | |
| ); | |
| return { | |
| ...authParams, | |
| folder, // Folder where file should be uploaded | |
| ...(fileType && { fileType }), | |
| }; | |
| } catch (error) { | |
| this.logger.error('Failed to generate authentication parameters', error); | |
| throw error; | |
| } | |
| } | |
| /** | |
| * Clean URL by removing query parameters | |
| * @param url - The URL to clean | |
| * @returns Clean URL without query parameters | |
| */ | |
| private cleanUrl(url: string): string { | |
| return url.split('?')[0]; | |
| } | |
| /** | |
| * Get file ID from ImageKit URL | |
| * @param url - The ImageKit file URL | |
| * @returns File details including fileId | |
| */ | |
| async getFileIdByUrl(url: string): Promise<any> { | |
| try { | |
| // Clean URL (remove query parameters like ?updatedAt=...) | |
| const cleanUrl = this.cleanUrl(url); | |
| const urlEndpoint = this.configService.get<string>( | |
| 'imagekitio.urlEndpoint', | |
| ); | |
| if (!cleanUrl.startsWith(urlEndpoint || '')) { | |
| throw new NotFoundException( | |
| 'Invalid ImageKit URL - does not match your URL endpoint', | |
| ); | |
| } | |
| // Extract file path from URL | |
| const filePath = cleanUrl.replace(urlEndpoint || '', ''); | |
| // Extract filename from path (last part after /) | |
| const fileName = filePath.split('/').pop() || ''; | |
| if (!fileName) { | |
| throw new NotFoundException('Could not extract filename from URL'); | |
| } | |
| // Search by filename | |
| const files = await this.imagekit.listFiles({ | |
| searchQuery: `name="${fileName}"`, | |
| }); | |
| if (!files || files.length === 0) { | |
| this.logger.warn(`File not found: ${fileName}`); | |
| throw new NotFoundException('File not found or already deleted'); | |
| } | |
| // Find exact match by comparing clean URLs | |
| const exactMatch = files.find( | |
| (file: any) => this.cleanUrl(file.url) === cleanUrl, | |
| ); | |
| if (!exactMatch) { | |
| this.logger.warn(`No exact match found for: ${fileName}`); | |
| throw new NotFoundException('File not found or already deleted'); | |
| } | |
| this.logger.log(`Found file: ${fileName}`); | |
| return exactMatch; | |
| } catch (error) { | |
| if (error instanceof NotFoundException) { | |
| throw error; | |
| } | |
| this.logger.error(`Failed to get file ID for URL: ${url}`, error); | |
| throw new NotFoundException('File not found or already deleted'); | |
| } | |
| } | |
| /** | |
| * Purge CDN cache for a URL | |
| * @param url - The ImageKit file URL to purge from cache | |
| * @returns Purge result | |
| */ | |
| async purgeCdnCache(url: string): Promise<any> { | |
| try { | |
| const cleanUrl = this.cleanUrl(url); | |
| const result = await this.imagekit.purgeCache(cleanUrl); | |
| this.logger.log(`Successfully purged CDN cache for: ${cleanUrl}`); | |
| return result; | |
| } catch (error) { | |
| this.logger.error(`Failed to purge CDN cache for URL: ${url}`, error); | |
| throw error; | |
| } | |
| } | |
| /** | |
| * Delete a file from ImageKit by URL and purge CDN cache | |
| * @param url - The ImageKit file URL | |
| * @returns Deletion result | |
| */ | |
| async deleteFileByUrl(url: string): Promise<any> { | |
| // First, get the file ID from the URL (this will throw NotFoundException if not found) | |
| const fileDetails = await this.getFileIdByUrl(url); | |
| if (!fileDetails || !fileDetails.fileId) { | |
| throw new NotFoundException('Could not retrieve file ID from URL'); | |
| } | |
| // Delete the file using the file ID | |
| const result = await this.deleteFile(fileDetails.fileId); | |
| // Purge CDN cache to make deletion immediate | |
| try { | |
| await this.purgeCdnCache(url); | |
| this.logger.log( | |
| `Successfully deleted file and purged cache for URL: ${url}`, | |
| ); | |
| } catch { | |
| this.logger.warn( | |
| `File deleted but CDN cache purge failed for: ${url}. Cache will expire naturally.`, | |
| ); | |
| } | |
| return { | |
| ...result, | |
| deletedUrl: url, | |
| fileId: fileDetails.fileId, | |
| }; | |
| } | |
| /** | |
| * Delete a file from ImageKit (private method used internally) | |
| * @param fileId - The file ID to delete | |
| * @returns Deletion result | |
| */ | |
| private async deleteFile(fileId: string): Promise<any> { | |
| try { | |
| const result = await this.imagekit.deleteFile(fileId); | |
| this.logger.log(`Successfully deleted file with ID: ${fileId}`); | |
| return result; | |
| } catch (error) { | |
| this.logger.error(`Failed to delete file with ID: ${fileId}`, error); | |
| throw error; | |
| } | |
| } | |
| } | |