Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x | import { v4 as uuidv4 } from 'uuid';
import { ImageStorageService } from './imageStorageService';
import { IImageStorageService } from './imageStorageService.interface';
import { IImageRepository } from '../repositories/imageRepository.interface';
import { ImageRepository } from '../repositories/imageRepository';
import { ImageMetadata } from '../models/imageMetadata.model';
import { getLogger } from '../common/logger';
import { ValidationError } from '../common/errors';
const logger = getLogger('ImageService');
const ALLOWED_IMAGE_TYPES = ['image/jpeg', 'image/png', 'image/webp'];
const PRESIGNED_URL_EXPIRATION = 1800;
export interface PresignUploadResult {
imageId: string;
uploadUrl: string;
}
export class ImageService {
private storageService: IImageStorageService;
private imageRepository: IImageRepository;
constructor(
storageService?: IImageStorageService,
imageRepository?: IImageRepository
) {
this.storageService = storageService || new ImageStorageService();
this.imageRepository = imageRepository || new ImageRepository();
}
/**
* Generate a presigned S3 upload URL for image upload
* @param originalName - Original filename provided by user
* @param contentType - MIME type of the image (e.g., image/jpeg)
* @returns Object with imageId and presigned upload URL
* @throws ValidationError if contentType is not supported
*/
async generatePresignedUpload(
originalName: string,
contentType: string
): Promise<PresignUploadResult> {
logger.info('generatePresignedUpload called', {
fileName: originalName,
fileType: contentType,
});
// Validate content type
Iif (!ALLOWED_IMAGE_TYPES.includes(contentType)) {
throw new ValidationError(`Unsupported content type: ${contentType}`);
}
const imageId = uuidv4();
// Generate presigned URL (5 min expiry)
const uploadUrl = await this.storageService.getPresignedUploadUrl(
imageId,
contentType,
PRESIGNED_URL_EXPIRATION
);
// Save initial metadata (status: pending upload)
const metadata: ImageMetadata = {
imageId,
originalName,
contentType,
size: 0, // Unknown until upload completes
url: '', // Public S3 URL will be set after upload
uploadedAt: new Date().toISOString(),
status: 'pending',
};
await this.imageRepository.save(metadata);
logger.info(`Presigned upload URL generated for: ${imageId}`);
return {
imageId,
uploadUrl,
};
}
/**
* Retrieve image metadata by ID
* @param imageId - Unique image identifier
* @returns Image metadata if found, null otherwise
*/
async getImageMetaData(imageId: string): Promise<ImageMetadata | null> {
logger.info('getImageMetaData called', { imageId });
// Check metadata exists
const metadata = await this.imageRepository.findById(imageId);
if (!metadata) {
return null;
}
// Retrieve from S3
// const image = await this.storageService.getImage(imageId);
logger.info(`getImageMetaData retrieved successfully: ${imageId}`);
return metadata;
}
/**
* Delete an image from S3 and its metadata from DynamoDB
* @param imageId - Unique image identifier
* @returns true if image was found and deleted, false if not found
*/
async deleteImage(imageId: string): Promise<boolean> {
logger.info('deleteImage called', { imageId });
// Check metadata exists
const metadata = await this.imageRepository.findById(imageId);
if (!metadata) {
logger.warn('deleteImage - image not found', { imageId });
return false;
}
// Delete from S3
await this.storageService.deleteImage(imageId);
// Delete metadata from DynamoDB
await this.imageRepository.delete(imageId);
logger.info(`Image deleted successfully: ${imageId}`);
return true;
}
}
|