Commit ef4c305c authored by duanjinfei's avatar duanjinfei

clean up storage

parent 4e1d8e24
// Follow this setup guide to integrate the Deno language server with your editor: import "jsr:@supabase/functions-js/edge-runtime.d.ts";
// https://deno.land/manual/getting_started/setup_your_environment import { createClient } from "jsr:@supabase/supabase-js@2";
// This enables autocomplete, go to definition, etc.
const corsHeaders = {
// Setup type definitions for built-in Supabase Runtime APIs "Access-Control-Allow-Origin": "*",
import "jsr:@supabase/functions-js/edge-runtime.d.ts" "Access-Control-Allow-Headers": "authorization, content-type",
import { createClient } from 'jsr:@supabase/supabase-js@2' "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
};
// 删除目录和其下的文件
async function deleteDirectory(bucket: string, directory: string) {
const { data, error } = await supabase.storage.from(bucket).list(directory, {
limit: 1000, // 批量获取文件
});
if (error) {
console.error(`Failed to list files in directory "${directory}":`, error.message);
return;
}
if (data && data.length > 0) { // 删除指定目录及其下的文件
const pathsToDelete = data.map((file) => `${directory}/${file.name}`); async function deleteDirectory(supabase, bucket: string, directory: string) {
const { error: deleteError } = await supabase.storage.from(bucket).remove(pathsToDelete); try {
const { data, error } = await supabase.storage.from(bucket).list(directory, {
limit: 1000,
});
if (deleteError) { if (error) {
console.error(`Failed to delete files in directory "${directory}":`, deleteError.message); console.error(`Failed to list files in directory "${directory}":`, error.message);
} else { return;
console.log(`Successfully deleted directory "${directory}" and its contents.`);
} }
if (data && data.length > 0) {
const pathsToDelete = data.map((file) => `${directory}/${file.name}`);
const { error: deleteError } = await supabase.storage.from(bucket).remove(pathsToDelete);
if (deleteError) {
console.error(`Failed to delete files in directory "${directory}":`, deleteError.message);
} else {
console.log(`Successfully deleted directory "${directory}" and its contents.`);
}
}
} catch (err) {
console.error(`Error deleting directory "${directory}":`, err);
} }
} }
// 获取需要删除的时间戳目录 // 获取当前整点时间
function getOldDirectories(currentTimestamp: string, interval: "hour" | "day", keepCount: number) { function getCurrentHourTimestamp(): Date {
const now = new Date(currentTimestamp); const now = new Date();
return new Date(now.getFullYear(), now.getMonth(), now.getDate(), now.getHours());
}
const oldTimestamps = []; // 获取东八区当天凌晨3点时间
for (let i = keepCount; i < keepCount + 7; i++) { function getTodayThreeAMTimestamp(): Date {
const oldDate = new Date(now); const now = new Date();
const today3AM = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 3, 0, 0);
if (interval === "hour") { // 如果当前时间小于凌晨3点,返回前一天的凌晨3点
oldDate.setHours(now.getHours() - i); if (now.getHours() < 3) {
} else if (interval === "day") { today3AM.setDate(today3AM.getDate() - 1);
oldDate.setDate(now.getDate() - i); }
}
oldTimestamps.push(oldDate.toISOString().split(":")[0]); // 精确到小时,避免分钟秒 return today3AM;
}
// 计算截止时间(7个周期之前的时间戳)
function getCutoffTimestamp(type: "app-category" | "user-rank"): number {
if (type === "app-category") {
const currentHour = getCurrentHourTimestamp();
const cutoff = new Date(currentHour);
cutoff.setHours(cutoff.getHours() - 7);
return cutoff.getTime();
} else {
const today3AM = getTodayThreeAMTimestamp();
const cutoff = new Date(today3AM);
cutoff.setDate(cutoff.getDate() - 7);
return cutoff.getTime();
} }
}
// 获取需要删除的目录
async function getDirectoriesToDelete(supabase, bucket: string, type: "app-category" | "user-rank"): Promise<string[]> {
try {
const { data, error } = await supabase.storage.from(bucket).list(`${type}/`, {
limit: 1000,
});
if (error) {
console.error(`Failed to list directories in ${type}:`, error.message);
return [];
}
const cutoffTimestamp = getCutoffTimestamp(type);
return oldTimestamps; const dirsToDelete = data
.filter(item => !item.name.includes('.')) // 只处理目录
.filter(item => {
const dirTimestamp = parseInt(item.name); // 转为秒
if (isNaN(dirTimestamp)) {
console.log(`Invalid timestamp directory: ${item.name}`);
return false;
}
const dirDate = new Date(dirTimestamp * 1000); // 转为日期
const shouldDelete = dirTimestamp * 1000 < cutoffTimestamp; // 确保单位统一为毫秒
console.log(`Directory "${item.name}" date: ${dirDate.toISOString()}, should delete: ${shouldDelete}`);
return shouldDelete;
})
.map(item => item.name);
console.log(`Found ${dirsToDelete.length} directories to delete for ${type}`);
return dirsToDelete;
} catch (err) {
console.error(`Error listing directories in ${type}:`, err);
return [];
}
} }
// 主逻辑处理
Deno.serve(async (req) => { Deno.serve(async (req) => {
if (req.method === 'OPTIONS') { if (req.method === "OPTIONS") {
return new Response('ok', { headers: corsHeaders }) return new Response("ok", { headers: corsHeaders });
} }
try { try {
// const { name } = await req.json() const supabaseUrl = Deno.env.get("SUPABASE_URL");
const supabase = createClient( const supabaseAnonKey = Deno.env.get("SUPABASE_ANON_KEY");
Deno.env.get('SUPABASE_URL') ?? '', const authHeader = req.headers.get("Authorization");
Deno.env.get('SUPABASE_ANON_KEY') ?? '',
{ global: { headers: { Authorization: req.headers.get('Authorization')! } } } if (!supabaseUrl || !supabaseAnonKey || !authHeader) {
) throw new Error("Missing Supabase configuration or Authorization header.");
}
const currentTimestamp = new Date().toISOString();
const supabase = createClient(supabaseUrl, supabaseAnonKey, {
// 处理 app-category:按小时保留前 7 个周期 global: { headers: { Authorization: authHeader } },
const oldAppCategoryDirs = getOldDirectories(currentTimestamp, "hour", 7); });
for (const dir of oldAppCategoryDirs) {
await deleteDirectory("cache", `app-category/${dir}`); console.log("Starting cleanup process...");
// 处理 app-category(每小时)
const appCategoryDirsToDelete = await getDirectoriesToDelete(supabase, "cache", "app-category");
for (const dir of appCategoryDirsToDelete) {
await deleteDirectory(supabase, "cache", `app-category/${dir}`);
} }
// 处理 user-rank:按天保留前 7 个周期 // 处理 user-rank(每天凌晨3点)
const oldUserRankDirs = getOldDirectories(currentTimestamp, "day", 7); const userRankDirsToDelete = await getDirectoriesToDelete(supabase, "cache", "user-rank");
for (const dir of oldUserRankDirs) { for (const dir of userRankDirsToDelete) {
await deleteDirectory("cache", `user-rank/${dir}`); await deleteDirectory(supabase, "cache", `user-rank/${dir}`);
} }
return new Response(JSON.stringify({ return new Response(
code: 200, JSON.stringify({
data: null, code: 200,
message: 'success' data: {
}), { appCategory: {
headers: { 'Content-Type': 'application/json' }, deletedDirs: appCategoryDirsToDelete,
status: 200, cutoffTime: new Date(getCutoffTimestamp("app-category")).toISOString(),
}) },
userRank: {
deletedDirs: userRankDirsToDelete,
cutoffTime: new Date(getCutoffTimestamp("user-rank")).toISOString(),
},
},
message: "success",
}),
{ headers: { ...corsHeaders, "Content-Type": "application/json" }, status: 200 }
);
} catch (err) { } catch (err) {
return new Response(String(err?.message ?? err), { status: 500 }) console.error("Error in cleanup process:", err);
return new Response(
JSON.stringify({ code: 500, message: err.message, data: null }),
{ headers: { ...corsHeaders, "Content-Type": "application/json" }, status: 500 }
);
} }
}) });
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment