UNPKG

@prodbirdy/mockup-generator

Version:

Serverless-optimized TypeScript SDK for generating high-quality product mockups from PSD templates

259 lines (228 loc) 6.62 kB
/** * Storage Manager for MockupSDK * Handles R2/S3 operations with better error handling and URL-based operations */ import { EventEmitter } from "events"; import { R2Client, generateUniqueFileName } from "../utils/r2-helpers"; import type { StorageConfig, ExportFormat } from "./types"; import { StorageError } from "./types"; export class StorageManager extends EventEmitter { private client: R2Client | null = null; private config: StorageConfig | null = null; constructor(config?: StorageConfig) { super(); if (config) { this.config = config; this.client = new R2Client(config); } else { // Try to load from environment variables this.loadFromEnvironment(); } } /** * Upload a PSD buffer to storage */ async uploadPSD(psdBuffer: Buffer, prefix = "mockups"): Promise<string> { if (!this.client) { throw new StorageError("Storage not configured"); } try { this.emit("progress", "uploading-psd", 0); const fileName = generateUniqueFileName("mockup", "psd"); const result = await this.client.uploadPSD( psdBuffer, fileName, prefix, false ); this.emit("progress", "uploading-psd", 100); return result.url; } catch (error) { throw new StorageError( `Failed to upload PSD: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } /** * Upload an exported image to storage */ async uploadExport( buffer: Buffer, format: ExportFormat, prefix = "exports" ): Promise<{ url: string; key: string; bucket: string }> { if (!this.client) { throw new StorageError("Storage not configured"); } try { const fileName = generateUniqueFileName("export", format); const result = await this.client.uploadImage( buffer, fileName, format as any, prefix, false ); return { url: result.url, key: result.key, bucket: result.bucket, }; } catch (error) { throw new StorageError( `Failed to upload ${format}: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } /** * Download a PSD from storage */ async downloadPSD(url: string): Promise<Buffer> { try { this.emit("progress", "downloading-psd", 0); // If it's a URL, fetch directly if (url.startsWith("http")) { const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const arrayBuffer = await response.arrayBuffer(); const buffer = Buffer.from(arrayBuffer); this.emit("progress", "downloading-psd", 100); return buffer; } // If it's a key, use the R2 client if (!this.client) { throw new StorageError( "Storage not configured for key-based downloads" ); } const buffer = await this.client.download(url, false); this.emit("progress", "downloading-psd", 100); return buffer; } catch (error) { throw new StorageError( `Failed to download PSD: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } /** * Download an image for smart object replacement */ async downloadImage(url: string): Promise<Buffer> { try { const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const arrayBuffer = await response.arrayBuffer(); return Buffer.from(arrayBuffer); } catch (error) { throw new StorageError( `Failed to download image: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } /** * Get storage information */ async getInfo(): Promise<{ bucket: string; region?: string; connected: boolean; }> { if (!this.client || !this.config) { return { bucket: "", connected: false, }; } try { // Test connection by listing objects (or another lightweight operation) await this.client.upload( { key: "health-check", body: "test", contentType: "text/plain", }, false ); // Clean up test file // Note: R2Client would need a delete method for this return { bucket: this.config.bucket, region: this.config.region, connected: true, }; } catch { return { bucket: this.config.bucket, region: this.config.region, connected: false, }; } } /** * Generate a signed URL for temporary access */ async getSignedUrl(key: string, expiresIn = 3600): Promise<string> { if (!this.client) { throw new StorageError("Storage not configured"); } try { return await this.client.getSignedUrl(key, expiresIn); } catch (error) { throw new StorageError( `Failed to generate signed URL: ${ error instanceof Error ? error.message : String(error) }`, error instanceof Error ? error : undefined ); } } /** * Check if storage is configured and available */ isAvailable(): boolean { return this.client !== null && this.config !== null; } /** * Cleanup resources (if needed) */ async cleanup(): Promise<void> { // The R2Client doesn't have persistent connections to clean up // But this method is here for future use or if we add connection pooling } // Private methods private loadFromEnvironment(): void { const endpoint = process.env.R2_ENDPOINT || process.env.S3_ENDPOINT; const accessKeyId = process.env.R2_ACCESS_KEY_ID || process.env.AWS_ACCESS_KEY_ID; const secretAccessKey = process.env.R2_SECRET_ACCESS_KEY || process.env.AWS_SECRET_ACCESS_KEY; const bucket = process.env.R2_BUCKET || process.env.S3_BUCKET; const region = process.env.R2_REGION || process.env.AWS_REGION; if (endpoint && accessKeyId && secretAccessKey && bucket) { this.config = { endpoint, accessKeyId, secretAccessKey, bucket, region, }; this.client = new R2Client(this.config); } } }