/** * One-time migration script: uploads/ -> S3-compatible object storage * * Reads all image files from the local uploads/ directory and uploads each * to the S3 bucket via the storage service. Preserves original filenames * as object keys. * * Prerequisites: * - S3_ENDPOINT, S3_ACCESS_KEY, S3_SECRET_KEY env vars must be set * - The S3 bucket must exist * * Usage: * bun run scripts/migrate-images-to-s3.ts */ import { readdir } from "node:fs/promises"; import { extname, join } from "node:path"; import { uploadImage } from "../src/server/services/storage.service"; const UPLOADS_DIR = "uploads"; const CONTENT_TYPE_MAP: Record = { ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".png": "image/png", ".webp": "image/webp", }; const IMAGE_EXTENSIONS = new Set(Object.keys(CONTENT_TYPE_MAP)); async function main() { // Check if uploads/ directory exists try { await readdir(UPLOADS_DIR); } catch { console.log("No uploads directory found. Nothing to migrate."); process.exit(0); } // Read all files const allFiles = await readdir(UPLOADS_DIR); const imageFiles = allFiles.filter((f) => IMAGE_EXTENSIONS.has(extname(f).toLowerCase()), ); if (imageFiles.length === 0) { console.log("No image files found in uploads/. Nothing to migrate."); process.exit(0); } console.log(`Found ${imageFiles.length} images to migrate\n`); let success = 0; let failed = 0; for (const filename of imageFiles) { const filePath = join(UPLOADS_DIR, filename); const ext = extname(filename).toLowerCase(); const contentType = CONTENT_TYPE_MAP[ext] ?? "application/octet-stream"; try { const file = Bun.file(filePath); const buffer = await file.arrayBuffer(); await uploadImage(Buffer.from(buffer), filename, contentType); success++; console.log(` Migrated: ${filename}`); } catch (err) { failed++; console.error( ` FAILED: ${filename} - ${err instanceof Error ? err.message : String(err)}`, ); } } console.log( `\nMigration complete: ${success}/${imageFiles.length} files migrated, ${failed} failures`, ); if (failed > 0) { console.log("Re-run this script to retry failed uploads."); } console.log( "\nOriginal files preserved in uploads/. Delete manually after verifying: rm -rf uploads/", ); } main().catch((err) => { console.error("Migration failed:", err); process.exit(1); });