index.ts 5.08 KB
Newer Older
duanjinfei's avatar
duanjinfei committed
1 2 3 4 5 6 7 8
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
import { createClient } from "jsr:@supabase/supabase-js@2";

const corsHeaders = {
  "Access-Control-Allow-Origin": "*",
  "Access-Control-Allow-Headers": "authorization, content-type",
  "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
};
duanjinfei's avatar
duanjinfei committed
9

duanjinfei's avatar
duanjinfei committed
10 11 12 13 14 15
// 删除指定目录及其下的文件
async function deleteDirectory(supabase, bucket: string, directory: string) {
  try {
    const { data, error } = await supabase.storage.from(bucket).list(directory, {
      limit: 1000,
    });
duanjinfei's avatar
duanjinfei committed
16

duanjinfei's avatar
duanjinfei committed
17 18 19
    if (error) {
      console.error(`Failed to list files in directory "${directory}":`, error.message);
      return;
duanjinfei's avatar
duanjinfei committed
20
    }
duanjinfei's avatar
duanjinfei committed
21 22 23 24 25 26 27 28 29 30 31 32 33

    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);
duanjinfei's avatar
duanjinfei committed
34 35 36
  }
}

duanjinfei's avatar
duanjinfei committed
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
// 计算特定周期的保留时间戳
function calculateRetentionTimestamp(cycle: 'daily' | 'hourly'): {
  retentionTimestamp: number,
  currentPeriodTimestamp: number
} {
  const now = new Date()

  let currentPeriodTimestamp: number
  let retentionTimestamp: number

  if (cycle === 'daily') {
    // 当前周期的时间戳(每天凌晨3点)
    const currentPeriod = new Date(now)
    currentPeriod.setHours(3, 0, 0, 0)
    currentPeriodTimestamp = currentPeriod.getTime() / 1000

    // 保留最近7天的数据,即7个凌晨3点周期
    const retentionPeriod = new Date(now)
    retentionPeriod.setDate(retentionPeriod.getDate() - 7)
    retentionPeriod.setHours(3, 0, 0, 0)
    retentionTimestamp = retentionPeriod.getTime() / 1000
  } else {
    // 当前周期的时间戳(每小时整点)
    const currentPeriod = new Date(now)
    currentPeriod.setMinutes(0, 0, 0)
    currentPeriodTimestamp = currentPeriod.getTime() / 1000

    // 保留最近7个小时的数据
    const retentionPeriod = new Date(now)
    retentionPeriod.setHours(retentionPeriod.getHours() - 7, 0, 0, 0)
    retentionTimestamp = retentionPeriod.getTime() / 1000
duanjinfei's avatar
duanjinfei committed
68
  }
duanjinfei's avatar
duanjinfei committed
69

duanjinfei's avatar
duanjinfei committed
70
  return { retentionTimestamp, currentPeriodTimestamp }
duanjinfei's avatar
duanjinfei committed
71 72
}

duanjinfei's avatar
duanjinfei committed
73 74 75 76 77
// 清理存储桶中过期数据
async function cleanupStorage(supabase: any, bucketName: string, category: 'app-category' | 'user-rank') {
  const cycle = category === 'app-category' ? 'daily' : 'hourly'
  const { retentionTimestamp, currentPeriodTimestamp } = calculateRetentionTimestamp(cycle)
  const deletedDirectories: string[] = []
duanjinfei's avatar
duanjinfei committed
78 79

  try {
duanjinfei's avatar
duanjinfei committed
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
    // 获取指定目录下的所有子目录
    const { data, error } = await supabase.storage.from(bucketName).list(category + '/', {
      limit: 100,
      offset: 0
    })

    if (error) throw error

    // 过滤并删除过期目录
    for (const item of data) {
      const dirTimestamp = parseInt(item.name)

      // 确保不删除当前周期的目录,且删除早于保留时间的目录
      if (dirTimestamp < retentionTimestamp && dirTimestamp !== currentPeriodTimestamp) {
        await deleteDirectory(supabase, bucketName, `${category}/${item.name}`)
        deletedDirectories.push(item.name)
        console.log(`删除过期目录: ${category}/${item.name}`)
      }
duanjinfei's avatar
duanjinfei committed
98 99
    }

duanjinfei's avatar
duanjinfei committed
100 101 102 103 104 105 106
    return {
      category,
      cycle,
      totalDeleted: deletedDirectories.length,
      deletedDirectories,
      currentPeriodTimestamp: currentPeriodTimestamp.toString()
    }
duanjinfei's avatar
duanjinfei committed
107
  } catch (err) {
duanjinfei's avatar
duanjinfei committed
108 109 110 111 112 113 114
    console.error(`清理 ${category} 存储时发生错误:`, err)
    return {
      category,
      cycle,
      totalDeleted: 0,
      error: err.message
    }
duanjinfei's avatar
duanjinfei committed
115
  }
duanjinfei's avatar
duanjinfei committed
116 117
}

duanjinfei's avatar
duanjinfei committed
118
// 主逻辑处理
duanjinfei's avatar
duanjinfei committed
119
Deno.serve(async (req) => {
duanjinfei's avatar
duanjinfei committed
120 121
  if (req.method === "OPTIONS") {
    return new Response("ok", { headers: corsHeaders });
duanjinfei's avatar
duanjinfei committed
122
  }
duanjinfei's avatar
duanjinfei committed
123

duanjinfei's avatar
duanjinfei committed
124
  try {
duanjinfei's avatar
duanjinfei committed
125 126 127 128 129 130 131 132 133 134 135 136 137 138
    const supabaseUrl = Deno.env.get("SUPABASE_URL");
    const supabaseAnonKey = Deno.env.get("SUPABASE_ANON_KEY");
    const authHeader = req.headers.get("Authorization");

    if (!supabaseUrl || !supabaseAnonKey || !authHeader) {
      throw new Error("Missing Supabase configuration or Authorization header.");
    }

    const supabase = createClient(supabaseUrl, supabaseAnonKey, {
      global: { headers: { Authorization: authHeader } },
    });

    console.log("Starting cleanup process...");

duanjinfei's avatar
duanjinfei committed
139 140 141
    // 清理存储并收集结果
    const appCategoryResult = await cleanupStorage(supabase, 'cache', 'app-category')
    const userRankResult = await cleanupStorage(supabase, 'cache', 'user-rank')
duanjinfei's avatar
duanjinfei committed
142

duanjinfei's avatar
duanjinfei committed
143 144
    return new Response(
      JSON.stringify({
duanjinfei's avatar
duanjinfei committed
145 146
        message: '存储清理完成',
        results: [appCategoryResult, userRankResult]
duanjinfei's avatar
duanjinfei committed
147
      }),
duanjinfei's avatar
duanjinfei committed
148 149 150 151
      {
        headers: { 'Content-Type': 'application/json' }
      }
    )
duanjinfei's avatar
duanjinfei committed
152
  } catch (err) {
duanjinfei's avatar
duanjinfei committed
153 154 155 156 157
    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 }
    );
duanjinfei's avatar
duanjinfei committed
158
  }
duanjinfei's avatar
duanjinfei committed
159
});