Session 2026-05-29: profile layout 2-col, file browser copy/paste, fbq-proxy/backfill fns, migrations, UI updates

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Krao Hasanee
2026-05-29 13:49:21 -04:00
parent 283511bf3a
commit c9998d4a8d
54 changed files with 2646 additions and 396 deletions
+69 -22
View File
@@ -19,8 +19,8 @@ const SUPABASE_URL = env.VITE_SUPABASE_URL || env.SUPABASE_URL;
const SERVICE_ROLE_KEY = env.SUPABASE_SERVICE_ROLE_KEY;
const FB_URL = (env.FILEBROWSER_URL || '').replace(/\/+$/, '');
const FB_TOKEN = env.FILEBROWSER_TOKEN;
const CLIENT_ROOT = env.FILEBROWSER_CLIENT_ROOT || '/fourgebranding/Clients';
const FB_SOURCE = 'files';
const CLIENT_ROOT = env.FILEBROWSER_CLIENT_ROOT || '/Clients';
const FB_SOURCE = 'srv';
if (!SUPABASE_URL || !SERVICE_ROLE_KEY) { console.error('Missing Supabase env'); process.exit(1); }
if (!FB_URL || !FB_TOKEN) { console.error('Missing FileBrowser env'); process.exit(1); }
@@ -67,7 +67,59 @@ async function mkdir(path) {
await fbFetch('POST', '/api/resources', { params: { path, isDir: 'true' } }).catch(() => {});
}
async function fbExists(path) {
try {
await fbFetch('GET', '/api/resources', { params: { path } });
return true;
} catch (e) {
if (String(e.message || '').includes('404')) return false;
throw e;
}
}
async function ensureTaskStructure(companyName, projectName, taskTitle) {
const companyDir = joinPath(CLIENT_ROOT, safeName(companyName));
const projectsDir = joinPath(companyDir, 'Projects');
const projectDir = joinPath(projectsDir, safeName(projectName));
const taskDir = joinPath(projectDir, safeName(taskTitle));
const requestInfoDir = joinPath(taskDir, 'Request Info');
const workingFilesDir = joinPath(taskDir, 'Working Files');
const oldBooksDir = joinPath(taskDir, 'Old Books');
await mkdir(companyDir);
await mkdir(projectsDir);
await mkdir(projectDir);
await mkdir(joinPath(projectDir, '00 Project Files'));
await mkdir(taskDir);
await mkdir(requestInfoDir);
await mkdir(workingFilesDir);
await mkdir(oldBooksDir);
return { requestInfoDir };
}
async function main() {
const { data: tasks, error: taskError } = await admin
.from('tasks')
.select(`
id, title,
project:projects!inner(
id, name,
company:companies!inner(name)
)
`);
if (taskError) { console.error('Task query failed:', taskError.message); process.exit(1); }
let ensuredTasks = 0;
for (const task of tasks || []) {
const project = task?.project;
const company = project?.company;
if (!task?.title || !project?.name || !company?.name) continue;
await ensureTaskStructure(company.name, project.name, task.title);
ensuredTasks++;
}
const { data: rows, error } = await admin
.from('submission_files')
.select(`
@@ -108,6 +160,7 @@ async function main() {
groups.get(key).files.push({ name: row.name, storage_path: row.storage_path });
}
console.log(`Ensured structure for ${ensuredTasks} tasks`);
console.log(`Found ${groups.size} task/revision groups, ${rows.length} total files`);
let processed = 0, skipped = 0, errors = 0;
@@ -116,38 +169,32 @@ async function main() {
if (group.files.length === 0) { skipped++; continue; }
const revFolder = `R${String(group.versionNumber).padStart(2, '0')}`;
const companyDir = joinPath(CLIENT_ROOT, safeName(group.companyName));
const projectDir = joinPath(companyDir, 'Projects', safeName(group.projectName));
const taskDir = joinPath(projectDir, safeName(group.taskTitle));
const requestInfoDir = joinPath(taskDir, 'Request Info');
const { requestInfoDir } = await ensureTaskStructure(group.companyName, group.projectName, group.taskTitle);
const revDir = joinPath(requestInfoDir, revFolder);
console.log(`\n[${group.companyName}] ${group.projectName} / ${group.taskTitle} / ${revFolder} (${group.files.length} files)`);
await mkdir(companyDir);
await mkdir(joinPath(companyDir, 'Projects'));
await mkdir(projectDir);
await mkdir(taskDir);
await mkdir(requestInfoDir);
await mkdir(revDir);
for (const file of group.files) {
try {
const { data: signed, error: signErr } = await admin.storage
const { data: blob, error: downloadErr } = await admin.storage
.from('submissions')
.createSignedUrl(file.storage_path, 120);
if (signErr || !signed?.signedUrl) throw new Error(`signed url failed: ${signErr?.message}`);
const fileRes = await fetch(signed.signedUrl);
if (!fileRes.ok) throw new Error(`download failed: ${fileRes.status}`);
const fileBuffer = await fileRes.arrayBuffer();
.download(file.storage_path);
if (downloadErr || !blob) throw new Error(`download failed: ${downloadErr?.message}`);
const fileBuffer = await blob.arrayBuffer();
const form = new FormData();
form.append('file', new Blob([fileBuffer]), file.name);
const fbFilePath = joinPath(revDir, file.name);
if (await fbExists(fbFilePath)) {
console.log(` - skip existing ${file.name}`);
skipped++;
continue;
}
await fbFetch('POST', '/api/resources', {
params: { path: fbFilePath, override: 'true' },
headers: { 'Content-Type': 'application/octet-stream' },
body: fileBuffer,
params: { path: fbFilePath },
body: form,
});
console.log(`${file.name}`);