Fix file sharing load speed and move error; misc updates
- Remove recursive directory size calculations (single Seafile API call per list) - Remove 'Used in this location' usage display - Fix move using v2 per-type endpoints instead of broken batch endpoint - Send entry type from frontend for correct move routing Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* cleanup-orphaned-storage.mjs
|
||||
*
|
||||
* Removes files in the `submissions` storage bucket that have no matching
|
||||
* row in `submission_files`. These are orphaned uploads from before the
|
||||
* silent-failure bug was fixed.
|
||||
*
|
||||
* Usage:
|
||||
* SUPABASE_SERVICE_ROLE_KEY=<your-key> node scripts/cleanup-orphaned-storage.mjs
|
||||
*
|
||||
* The service role key is in Supabase dashboard → Project Settings → API.
|
||||
* Run once, then you can delete this script if you like.
|
||||
*/
|
||||
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
|
||||
const SUPABASE_URL = 'https://fqflxxqvennhvoeywrdw.supabase.co';
|
||||
const SERVICE_ROLE_KEY = process.env.SUPABASE_SERVICE_ROLE_KEY;
|
||||
|
||||
if (!SERVICE_ROLE_KEY) {
|
||||
console.error('Error: SUPABASE_SERVICE_ROLE_KEY env var is required.');
|
||||
console.error(' SUPABASE_SERVICE_ROLE_KEY=<key> node scripts/cleanup-orphaned-storage.mjs');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const supabase = createClient(SUPABASE_URL, SERVICE_ROLE_KEY);
|
||||
|
||||
async function listAllStorageFiles(bucket) {
|
||||
// Storage structure: submissions/{taskId}/{timestamp}_{filename}
|
||||
// List the root to get task-ID "folders", then list each folder for files.
|
||||
const allPaths = [];
|
||||
|
||||
const { data: folders, error: folderError } = await supabase.storage
|
||||
.from(bucket)
|
||||
.list('', { limit: 1000, sortBy: { column: 'name', order: 'asc' } });
|
||||
|
||||
if (folderError) throw new Error(`Failed to list root: ${folderError.message}`);
|
||||
if (!folders?.length) return allPaths;
|
||||
|
||||
for (const folder of folders) {
|
||||
// Supabase returns folders as items with `id: null`
|
||||
if (folder.id !== null) {
|
||||
// It's a top-level file (rare, skip)
|
||||
allPaths.push(folder.name);
|
||||
continue;
|
||||
}
|
||||
|
||||
const { data: files, error: fileError } = await supabase.storage
|
||||
.from(bucket)
|
||||
.list(folder.name, { limit: 1000, sortBy: { column: 'name', order: 'asc' } });
|
||||
|
||||
if (fileError) {
|
||||
console.warn(` Warning: failed to list ${folder.name}/: ${fileError.message}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const file of files ?? []) {
|
||||
if (file.id !== null) {
|
||||
allPaths.push(`${folder.name}/${file.name}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return allPaths;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('Fetching all submission_files records from database...');
|
||||
const { data: dbFiles, error: dbError } = await supabase
|
||||
.from('submission_files')
|
||||
.select('storage_path')
|
||||
.not('storage_path', 'is', null);
|
||||
|
||||
if (dbError) throw new Error(`DB query failed: ${dbError.message}`);
|
||||
|
||||
const knownPaths = new Set((dbFiles ?? []).map(r => r.storage_path));
|
||||
console.log(` ${knownPaths.size} file records found in database.`);
|
||||
|
||||
console.log('\nListing all files in submissions storage bucket...');
|
||||
const storagePaths = await listAllStorageFiles('submissions');
|
||||
console.log(` ${storagePaths.length} files found in storage.`);
|
||||
|
||||
const orphans = storagePaths.filter(p => !knownPaths.has(p));
|
||||
console.log(`\n${orphans.length} orphaned file(s) found (in storage but not in DB).`);
|
||||
|
||||
if (orphans.length === 0) {
|
||||
console.log('Nothing to clean up.');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('\nOrphaned files:');
|
||||
orphans.forEach(p => console.log(` ${p}`));
|
||||
|
||||
// Delete in batches of 100 (Supabase limit)
|
||||
const BATCH = 100;
|
||||
let deleted = 0;
|
||||
for (let i = 0; i < orphans.length; i += BATCH) {
|
||||
const batch = orphans.slice(i, i + BATCH);
|
||||
const { error: delError } = await supabase.storage.from('submissions').remove(batch);
|
||||
if (delError) {
|
||||
console.error(` Error deleting batch: ${delError.message}`);
|
||||
} else {
|
||||
deleted += batch.length;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\nDone. ${deleted}/${orphans.length} orphaned file(s) deleted.`);
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error('Fatal error:', err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user