#!/usr/bin/env node // Backfill FileBrowser folders for all existing projects and tasks. // Project: Clients/{company}/Projects/{project}/00 Project Files/ + 00 Project Info/ // Task: Clients/{company}/Projects/{project}/{task}/Working Files/ + Request Info/ // Run: node --env-file=.env.backfill scripts/backfill-project-folders.mjs const FB_SOURCE = 'files'; const FILEBROWSER_URL = (process.env.FILEBROWSER_URL || 'https://fourgebranding.krao.us').replace(/\/+$/, ''); const FILEBROWSER_TOKEN = process.env.FILEBROWSER_TOKEN; const CLIENT_ROOT = process.env.FILEBROWSER_CLIENT_ROOT || '/fourgebranding/Clients'; const SUPABASE_URL = process.env.VITE_SUPABASE_URL || process.env.SUPABASE_URL; const SUPABASE_KEY = process.env.SUPABASE_SERVICE_ROLE_KEY; if (!FILEBROWSER_TOKEN) { console.error('Missing FILEBROWSER_TOKEN'); process.exit(1); } if (!SUPABASE_URL || !SUPABASE_KEY) { console.error('Missing Supabase env'); process.exit(1); } function safeName(value) { return String(value || '') .trim() .replace(/[\\/:*?"<>|#%{}^~[\]`]+/g, '-') .replace(/\s+/g, ' ') .replace(/^-+|-+$/g, ''); } function normalizePath(path) { const parts = String(path || '/').split('/').filter(p => p && p !== '.' && p !== '..'); return `/${parts.join('/')}`; } function joinPath(...parts) { return normalizePath(parts.join('/')); } async function mkdir(path) { const qs = new URLSearchParams({ source: FB_SOURCE, path, isDir: 'true' }).toString(); const res = await fetch(`${FILEBROWSER_URL}/api/resources?${qs}`, { method: 'POST', headers: { Authorization: `Bearer ${FILEBROWSER_TOKEN}` }, }); // 200 = created, 409 = already exists — both fine if (!res.ok && res.status !== 409) { const text = await res.text(); throw new Error(`mkdir ${path} failed (${res.status}): ${text}`); } return res.status; } async function supabaseFetch(path) { const res = await fetch(`${SUPABASE_URL}/rest/v1/${path}`, { headers: { apikey: SUPABASE_KEY, Authorization: `Bearer ${SUPABASE_KEY}`, }, }); if (!res.ok) throw new Error(`Supabase ${path}: ${await res.text()}`); return res.json(); } async function run() { console.log('Fetching projects from Supabase...'); const projects = await supabaseFetch('projects?select=id,name,company:companies(name)&order=created_at.asc'); console.log(`Found ${projects.length} projects.`); console.log('Fetching tasks from Supabase...'); const tasks = await supabaseFetch('tasks?select=id,title,project:projects(name,company:companies(name))&order=submitted_at.asc'); console.log(`Found ${tasks.length} tasks.\n`); let created = 0; let existing = 0; let errors = 0; // ── Projects ────────────────────────────────────────────────────────────── console.log('=== PROJECTS ==='); for (const project of projects) { const companyName = project.company?.name; if (!companyName) { console.log(` SKIP ${project.name} — no company`); continue; } const companyDir = joinPath(CLIENT_ROOT, safeName(companyName)); const projectsDir = joinPath(companyDir, 'Projects'); const projectDir = joinPath(projectsDir, safeName(project.name)); try { await mkdir(companyDir); await mkdir(projectsDir); const s = await mkdir(projectDir); await mkdir(joinPath(projectDir, '00 Project Files')); await mkdir(joinPath(projectDir, '00 Project Info')); if (s === 409) { console.log(` EXISTS ${companyName} / ${project.name}`); existing++; } else { console.log(` CREATED ${companyName} / ${project.name}`); created++; } } catch (err) { console.error(` ERROR ${companyName} / ${project.name}: ${err.message}`); errors++; } } // ── Tasks ───────────────────────────────────────────────────────────────── console.log('\n=== TASKS ==='); for (const task of tasks) { const projectName = task.project?.name; const companyName = task.project?.company?.name; if (!projectName || !companyName) { console.log(` SKIP ${task.title} — missing project/company`); continue; } const projectDir = joinPath(CLIENT_ROOT, safeName(companyName), 'Projects', safeName(projectName)); const taskDir = joinPath(projectDir, safeName(task.title)); try { // Ensure parent exists (idempotent) await mkdir(joinPath(CLIENT_ROOT, safeName(companyName))); await mkdir(joinPath(CLIENT_ROOT, safeName(companyName), 'Projects')); await mkdir(projectDir); const s = await mkdir(taskDir); await mkdir(joinPath(taskDir, 'Working Files')); await mkdir(joinPath(taskDir, 'Request Info')); if (s === 409) { console.log(` EXISTS ${companyName} / ${projectName} / ${task.title}`); existing++; } else { console.log(` CREATED ${companyName} / ${projectName} / ${task.title}`); created++; } } catch (err) { console.error(` ERROR ${companyName} / ${projectName} / ${task.title}: ${err.message}`); errors++; } } console.log(`\nDone. Created: ${created} Already existed: ${existing} Errors: ${errors}`); } run().catch(err => { console.error(err); process.exit(1); });