This commit is contained in:
150
scripts/legacy/recover_orphaned_projects.ts
Normal file
150
scripts/legacy/recover_orphaned_projects.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
|
||||
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();
|
||||
Reference in New Issue
Block a user