/** * S3 Storage Provider for LocalGreenChain * Agent 3: File Upload & Storage System * * Compatible with AWS S3, Cloudflare R2, and MinIO */ import { S3Client, PutObjectCommand, DeleteObjectCommand, HeadObjectCommand, GetObjectCommand, } from '@aws-sdk/client-s3'; import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; import { StorageConfig, StorageProviderInterface } from '../types'; export class S3StorageProvider implements StorageProviderInterface { private client: S3Client; private bucket: string; private publicUrl?: string; constructor(config: StorageConfig) { this.bucket = config.bucket; this.publicUrl = config.publicUrl; const clientConfig: ConstructorParameters[0] = { region: config.region || 'us-east-1', }; // Add endpoint for R2/MinIO if (config.endpoint) { clientConfig.endpoint = config.endpoint; clientConfig.forcePathStyle = config.provider === 'minio'; } // Add credentials if provided if (config.accessKeyId && config.secretAccessKey) { clientConfig.credentials = { accessKeyId: config.accessKeyId, secretAccessKey: config.secretAccessKey, }; } this.client = new S3Client(clientConfig); } async upload(key: string, buffer: Buffer, contentType: string): Promise { const command = new PutObjectCommand({ Bucket: this.bucket, Key: key, Body: buffer, ContentType: contentType, CacheControl: 'public, max-age=31536000', // 1 year cache }); await this.client.send(command); return this.getPublicUrl(key); } async delete(key: string): Promise { try { const command = new DeleteObjectCommand({ Bucket: this.bucket, Key: key, }); await this.client.send(command); return true; } catch (error) { console.error('Error deleting file from S3:', error); return false; } } async getSignedUrl(key: string, expiresIn = 3600): Promise { const command = new GetObjectCommand({ Bucket: this.bucket, Key: key, }); return getSignedUrl(this.client, command, { expiresIn }); } async getPresignedUploadUrl(key: string, contentType: string, expiresIn = 3600): Promise { const command = new PutObjectCommand({ Bucket: this.bucket, Key: key, ContentType: contentType, }); return getSignedUrl(this.client, command, { expiresIn }); } async exists(key: string): Promise { try { const command = new HeadObjectCommand({ Bucket: this.bucket, Key: key, }); await this.client.send(command); return true; } catch { return false; } } getPublicUrl(key: string): string { if (this.publicUrl) { return `${this.publicUrl}/${key}`; } return `https://${this.bucket}.s3.amazonaws.com/${key}`; } }