新增資料庫架構
This commit is contained in:
221
scripts/clear-storage.js
Normal file
221
scripts/clear-storage.js
Normal file
@@ -0,0 +1,221 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* 心願星河 - 清空 Supabase Storage
|
||||
*
|
||||
* ⚠️ 警告:此腳本將永久刪除所有存儲的圖片文件!
|
||||
*
|
||||
* 使用方法:
|
||||
* 1. 確保已安裝依賴:npm install
|
||||
* 2. 設置環境變數或在 .env.local 中配置 Supabase 連接
|
||||
* 3. 執行腳本:node scripts/clear-storage.js
|
||||
*/
|
||||
|
||||
const { createClient } = require('@supabase/supabase-js');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// 載入環境變數
|
||||
require('dotenv').config({ path: '.env.local' });
|
||||
|
||||
// Supabase 配置
|
||||
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
|
||||
const supabaseServiceKey = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
|
||||
|
||||
// 檢查必要的環境變數
|
||||
if (!supabaseUrl || !supabaseServiceKey) {
|
||||
console.error('❌ 錯誤:缺少必要的環境變數');
|
||||
console.error('請確保已設置以下環境變數:');
|
||||
console.error('- NEXT_PUBLIC_SUPABASE_URL');
|
||||
console.error('- SUPABASE_SERVICE_ROLE_KEY 或 NEXT_PUBLIC_SUPABASE_ANON_KEY');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 初始化 Supabase 客戶端
|
||||
const supabase = createClient(supabaseUrl, supabaseServiceKey);
|
||||
|
||||
// 存儲桶名稱
|
||||
const BUCKETS = ['wish-images', 'wish-thumbnails'];
|
||||
|
||||
/**
|
||||
* 清空指定存儲桶中的所有文件
|
||||
*/
|
||||
async function clearBucket(bucketName) {
|
||||
try {
|
||||
console.log(`\n🗂️ 正在處理存儲桶:${bucketName}`);
|
||||
|
||||
// 列出所有文件
|
||||
const { data: files, error: listError } = await supabase.storage
|
||||
.from(bucketName)
|
||||
.list('', {
|
||||
limit: 1000,
|
||||
sortBy: { column: 'created_at', order: 'desc' }
|
||||
});
|
||||
|
||||
if (listError) {
|
||||
console.error(`❌ 列出 ${bucketName} 文件時出錯:`, listError.message);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!files || files.length === 0) {
|
||||
console.log(`✅ ${bucketName} 已經是空的`);
|
||||
return true;
|
||||
}
|
||||
|
||||
console.log(`📊 找到 ${files.length} 個文件`);
|
||||
|
||||
// 獲取所有文件路徑(包括子目錄)
|
||||
const allFilePaths = [];
|
||||
|
||||
for (const file of files) {
|
||||
if (file.name && file.name !== '.emptyFolderPlaceholder') {
|
||||
// 如果是目錄,遞歸獲取其中的文件
|
||||
if (!file.metadata) {
|
||||
const { data: subFiles, error: subListError } = await supabase.storage
|
||||
.from(bucketName)
|
||||
.list(file.name, { limit: 1000 });
|
||||
|
||||
if (!subListError && subFiles) {
|
||||
subFiles.forEach(subFile => {
|
||||
if (subFile.name && subFile.name !== '.emptyFolderPlaceholder') {
|
||||
allFilePaths.push(`${file.name}/${subFile.name}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
allFilePaths.push(file.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (allFilePaths.length === 0) {
|
||||
console.log(`✅ ${bucketName} 中沒有需要刪除的文件`);
|
||||
return true;
|
||||
}
|
||||
|
||||
console.log(`🗑️ 準備刪除 ${allFilePaths.length} 個文件...`);
|
||||
|
||||
// 批量刪除文件
|
||||
const batchSize = 50; // Supabase 建議的批量操作大小
|
||||
let totalDeleted = 0;
|
||||
let hasErrors = false;
|
||||
|
||||
for (let i = 0; i < allFilePaths.length; i += batchSize) {
|
||||
const batch = allFilePaths.slice(i, i + batchSize);
|
||||
|
||||
const { data, error } = await supabase.storage
|
||||
.from(bucketName)
|
||||
.remove(batch);
|
||||
|
||||
if (error) {
|
||||
console.error(`❌ 刪除批次 ${Math.floor(i/batchSize) + 1} 時出錯:`, error.message);
|
||||
hasErrors = true;
|
||||
} else {
|
||||
totalDeleted += batch.length;
|
||||
console.log(`✅ 已刪除批次 ${Math.floor(i/batchSize) + 1}/${Math.ceil(allFilePaths.length/batchSize)} (${batch.length} 個文件)`);
|
||||
}
|
||||
|
||||
// 避免請求過於頻繁
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
}
|
||||
|
||||
console.log(`📊 ${bucketName} 清空完成:刪除了 ${totalDeleted}/${allFilePaths.length} 個文件`);
|
||||
return !hasErrors;
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ 清空 ${bucketName} 時發生未預期錯誤:`, error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 驗證存儲桶是否存在
|
||||
*/
|
||||
async function verifyBuckets() {
|
||||
try {
|
||||
const { data: buckets, error } = await supabase.storage.listBuckets();
|
||||
|
||||
if (error) {
|
||||
console.error('❌ 無法獲取存儲桶列表:', error.message);
|
||||
return false;
|
||||
}
|
||||
|
||||
const existingBuckets = buckets.map(bucket => bucket.id);
|
||||
const missingBuckets = BUCKETS.filter(bucket => !existingBuckets.includes(bucket));
|
||||
|
||||
if (missingBuckets.length > 0) {
|
||||
console.warn('⚠️ 以下存儲桶不存在,將跳過:', missingBuckets.join(', '));
|
||||
return BUCKETS.filter(bucket => existingBuckets.includes(bucket));
|
||||
}
|
||||
|
||||
return BUCKETS;
|
||||
} catch (error) {
|
||||
console.error('❌ 驗證存儲桶時發生錯誤:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 主函數
|
||||
*/
|
||||
async function main() {
|
||||
console.log('🚀 開始清空 Supabase Storage...');
|
||||
console.log('⚠️ 警告:這將永久刪除所有存儲的圖片文件!');
|
||||
|
||||
// 驗證存儲桶
|
||||
const bucketsToProcess = await verifyBuckets();
|
||||
if (!bucketsToProcess || bucketsToProcess.length === 0) {
|
||||
console.error('❌ 沒有可處理的存儲桶');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`📋 將處理 ${bucketsToProcess.length} 個存儲桶:`, bucketsToProcess.join(', '));
|
||||
|
||||
// 給用戶 5 秒鐘考慮時間
|
||||
console.log('\n⏰ 5 秒後開始刪除... (按 Ctrl+C 取消)');
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
|
||||
let allSuccess = true;
|
||||
|
||||
// 清空每個存儲桶
|
||||
for (const bucket of bucketsToProcess) {
|
||||
const success = await clearBucket(bucket);
|
||||
if (!success) {
|
||||
allSuccess = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 顯示最終結果
|
||||
console.log('\n' + '='.repeat(50));
|
||||
if (allSuccess) {
|
||||
console.log('✅ 所有存儲桶清空完成!');
|
||||
} else {
|
||||
console.log('⚠️ 存儲桶清空完成,但過程中有一些錯誤');
|
||||
}
|
||||
|
||||
console.log('\n📝 建議後續步驟:');
|
||||
console.log('1. 在 Supabase Dashboard 中確認 Storage 已清空');
|
||||
console.log('2. 執行 clear-all-data.sql 清空資料庫');
|
||||
console.log('3. 重新啟動應用程式');
|
||||
}
|
||||
|
||||
// 錯誤處理
|
||||
process.on('unhandledRejection', (error) => {
|
||||
console.error('❌ 未處理的錯誤:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
console.log('\n❌ 用戶取消操作');
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
// 執行主函數
|
||||
if (require.main === module) {
|
||||
main().catch(error => {
|
||||
console.error('❌ 腳本執行失敗:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { clearBucket, verifyBuckets };
|
Reference in New Issue
Block a user