streamflix-api / src /shared /modules /upload /imagekitio /imagekitio.service.ts
Akshar2325
refactor(upload): reorganize storage modules under unified upload namespace
f75a84b
Raw
History Blame
6.13 kB
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import ImageKit from 'imagekit';
@Injectable()
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;
}
}
}