import * as fs from 'fs'; import * as path from 'path'; import { promisify } from 'util'; import { CloudStorageAdapter, UploadOptions, UploadResult, } from './storage-adapter.interface'; const mkdir = promisify(fs.mkdir); const writeFile = promisify(fs.writeFile); const readFile = promisify(fs.readFile); const unlink = promisify(fs.unlink); const access = promisify(fs.access); const copyFile = promisify(fs.copyFile); const rename = promisify(fs.rename); export interface LocalStorageConfig { basePath: string; baseUrl?: string; } export class LocalStorageAdapter implements CloudStorageAdapter { private basePath: string; private baseUrl: string; constructor(config: LocalStorageConfig) { this.basePath = config.basePath; this.baseUrl = config.baseUrl || 'http://localhost:3000/uploads'; this.ensureBasePath(); } private async ensureBasePath(): Promise { try { await access(this.basePath); } catch { await mkdir(this.basePath, { recursive: true }); } } private getFullPath(filePath: string): string { return path.join(this.basePath, filePath); } private async ensureDirectory(filePath: string): Promise { const dir = path.dirname(filePath); const fullDir = this.getFullPath(dir); try { await access(fullDir); } catch { await mkdir(fullDir, { recursive: true }); } } async upload( file: Buffer | NodeJS.ReadableStream, filename: string, options?: UploadOptions, ): Promise { const filePath = options?.path ? path.join(options.path, filename) : filename; const fullPath = this.getFullPath(filePath); await this.ensureDirectory(fullPath); if (file instanceof Buffer) { await writeFile(fullPath, file); } else { const writeStream = fs.createWriteStream(fullPath); const readable = file as NodeJS.ReadableStream; await new Promise((resolve, reject) => { readable.pipe(writeStream); readable.on('end', resolve); readable.on('error', reject); }); } const stats = fs.statSync(fullPath); return { url: `${this.baseUrl}/${filePath}`, path: filePath, size: stats.size, }; } async download(filePath: string): Promise { const fullPath = this.getFullPath(filePath); return readFile(fullPath); } async delete(filePath: string): Promise { const fullPath = this.getFullPath(filePath); await unlink(fullPath); } async exists(filePath: string): Promise { try { const fullPath = this.getFullPath(filePath); await access(fullPath); return true; } catch { return false; } } async getUrl( filePath: string, _bucket?: string, expiresIn?: number, ): Promise { if (expiresIn) { return `${this.baseUrl}/${filePath}?expires=${Date.now() + expiresIn * 1000}`; } return `${this.baseUrl}/${filePath}`; } async copy( sourcePath: string, targetPath: string, _sourceBucket?: string, _targetBucket?: string, ): Promise { const sourceFullPath = this.getFullPath(sourcePath); const targetFullPath = this.getFullPath(targetPath); await this.ensureDirectory(targetFullPath); await copyFile(sourceFullPath, targetFullPath); } async move( sourcePath: string, targetPath: string, _sourceBucket?: string, _targetBucket?: string, ): Promise { const sourceFullPath = this.getFullPath(sourcePath); const targetFullPath = this.getFullPath(targetPath); await this.ensureDirectory(targetFullPath); await rename(sourceFullPath, targetFullPath); } }