import { PrismaClient } from '@prisma/client'; import fs from 'fs'; import path from 'path'; const prisma = new PrismaClient(); const STORAGE_ROOT = '/Users/haruncan/Downloads/DEV/etsy-mastermind-engine-v13.1/storage'; async function recover() { console.log("šŸš‘ STARTING EMERGENCY RECOVERY V2..."); const adminUser = await prisma.user.findUnique({ where: { email: 'admin@digicraft.app' } }); if (!adminUser) { console.error("āŒ Admin user not found."); return; } const projectsDir = path.join(STORAGE_ROOT, 'projects'); if (!fs.existsSync(projectsDir)) return; const folders = fs.readdirSync(projectsDir).filter(f => fs.statSync(path.join(projectsDir, f)).isDirectory()); console.log(`šŸ“‚ Scanning ${folders.length} folders...`); let recoveredCount = 0; for (const projectId of folders) { // 1. Gather Metadata First const projectPath = path.join(projectsDir, projectId); const stats = fs.statSync(projectPath); const createdAt = stats.birthtime; // Niche Extraction let niche = `Recovered Project (${projectId.substring(0, 8)})`; const guidePath = path.join(projectPath, 'docs', 'Printing_Guide.txt'); if (fs.existsSync(guidePath)) { // Can be enhanced to read content later } // Assets Scan const masterDir = path.join(projectPath, 'master'); const masterFiles = fs.existsSync(masterDir) ? fs.readdirSync(masterDir).filter(f => f.endsWith('.png') || f.endsWith('.jpg')) : []; if (masterFiles.length === 0) { // Skip empty projects to avoid clutter? Or recover anyway? // Let's recover anyway but mark as draft? No, skip if no master. // console.warn(`Skipping ${projectId} (No Master)`); continue; } // 2. DB Operations const exists = await prisma.project.findUnique({ where: { id: projectId } }); if (exists) { // Repair Existing await prisma.project.update({ where: { id: projectId }, data: { userId: adminUser.id, status: "COMPLETED", niche: niche // Update niche incase it was generic } }); process.stdout.write('.'); } else { // Create New await prisma.project.create({ data: { id: projectId, userId: adminUser.id, niche: niche, productType: "Wall Art", creativity: "Balanced", aspectRatio: "3:4", status: "COMPLETED", createdAt: createdAt } }); process.stdout.write('+'); } // ALWAYS ENSURE SEO DATA (UPSERT) const seoDataPayload = { title: niche, description: "Recovered from storage.", keywords: "recovered, art, vintage", printingGuide: "Standard", suggestedPrice: "5.00", jsonLd: JSON.stringify({ "@context": "https://schema.org", "@type": "Product", "name": niche, "description": "Recovered from storage.", "offers": { "@type": "Offer", "price": "5.00", "priceCurrency": "USD" } }) }; const seoExists = await prisma.seoData.findUnique({ where: { projectId: projectId } }); if (!seoExists) { await prisma.seoData.create({ data: { ...seoDataPayload, projectId: projectId } }); } else { // Force update to ensure JSON-LD is there await prisma.seoData.update({ where: { projectId: projectId }, data: seoDataPayload }); process.stdout.write('s'); // seo update } // 4. Ensure Assets for (const file of masterFiles) { const assetPath = `projects/${projectId}/master/${file}`; const assetExists = await prisma.asset.findFirst({ where: { path: assetPath } }); if (!assetExists) { await prisma.asset.create({ data: { projectId: projectId, type: "master", // LOWERCASE to match API expectation path: assetPath, createdAt: fs.statSync(path.join(masterDir, file)).birthtime, quality: "MASTER" } }); } else { // Fix bad casing if exists if (assetExists.type === 'MASTER') { await prisma.asset.update({ where: { id: assetExists.id }, data: { type: 'master' } }); process.stdout.write('f'); // fix } } } recoveredCount++; } console.log(`\nšŸŽ‰ Processed ${recoveredCount} projects.`); await prisma.$disconnect(); } recover();