import fs from "fs/promises"; import path from "path"; import { nanoid } from "nanoid"; // Storage base path (local filesystem) const STORAGE_BASE_PATH = process.env.STORAGE_BASE_PATH || path.join(process.cwd(), "storage"); /** * Ensure storage directory exists */ async function ensureStorageDir(dirPath: string) { try { await fs.mkdir(dirPath, { recursive: true }); } catch (error) { console.error(`[LocalStorage] Failed to create directory ${dirPath}:`, error); throw error; } } /** * Generate a storage key with YYYY-MM prefix for organization */ export function generateStorageKey(userId: number, fileName: string): string { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, "0"); const randomId = nanoid(8); // Format: YYYY-MM/userId-randomId-filename return `${year}-${month}/${userId}-${randomId}-${fileName}`; } /** * Store a file in local storage * @param fileKey - Storage key (e.g., "2025-01/1-abc123-invoice.pdf") * @param buffer - File content as Buffer * @param contentType - MIME type (optional, for metadata) * @returns Object with key and public URL */ export async function localStoragePut( fileKey: string, buffer: Buffer, _contentType?: string ): Promise<{ key: string; url: string }> { try { const fullPath = path.join(STORAGE_BASE_PATH, fileKey); const dirPath = path.dirname(fullPath); // Ensure directory exists await ensureStorageDir(dirPath); // Write file await fs.writeFile(fullPath, buffer); // Generate public URL (served by Express static middleware) const url = `/storage/${fileKey}`; console.log(`[LocalStorage] File stored: ${fileKey}`); return { key: fileKey, url }; } catch (error) { console.error(`[LocalStorage] Failed to store file ${fileKey}:`, error); throw error; } } /** * Retrieve a file from local storage * @param fileKey - Storage key * @returns File content as Buffer */ export async function localStorageGet(fileKey: string): Promise { try { const fullPath = path.join(STORAGE_BASE_PATH, fileKey); const buffer = await fs.readFile(fullPath); return buffer; } catch (error) { console.error(`[LocalStorage] Failed to retrieve file ${fileKey}:`, error); throw error; } } /** * Delete a file from local storage * @param fileKey - Storage key */ export async function localStorageDelete(fileKey: string): Promise { try { const fullPath = path.join(STORAGE_BASE_PATH, fileKey); await fs.unlink(fullPath); console.log(`[LocalStorage] File deleted: ${fileKey}`); } catch (error) { console.error(`[LocalStorage] Failed to delete file ${fileKey}:`, error); throw error; } } /** * Check if a file exists in storage * @param fileKey - Storage key * @returns true if file exists, false otherwise */ export async function localStorageExists(fileKey: string): Promise { try { const fullPath = path.join(STORAGE_BASE_PATH, fileKey); await fs.access(fullPath); return true; } catch { return false; } }