Files
wish-pool/lib/supabase-service-updated.ts
2025-07-19 02:12:37 +08:00

300 lines
9.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { supabase, type Database } from "./supabase"
import { SupabaseImageService, ImageMigrationService, type SupabaseImageFile } from "./supabase-image-utils"
// 更新的 Wish 類型定義
export type Wish = Database["public"]["Tables"]["wishes"]["Row"] & {
like_count?: number
images?: SupabaseImageFile[] // 使用新的圖片類型
}
export type WishInsert = Database["public"]["Tables"]["wishes"]["Insert"]
export type WishLike = Database["public"]["Tables"]["wish_likes"]["Row"]
export type UserSettings = Database["public"]["Tables"]["user_settings"]["Row"]
// 錯誤處理
export class SupabaseError extends Error {
constructor(
message: string,
public originalError?: any,
) {
super(message)
this.name = "SupabaseError"
}
}
// 更新的困擾案例服務
export class WishService {
// 獲取所有公開的困擾案例(帶點讚數和圖片)
static async getPublicWishes(): Promise<Wish[]> {
try {
const { data, error } = await supabase
.from("wishes_with_likes")
.select("*")
.eq("is_public", true)
.order("created_at", { ascending: false })
if (error) throw new SupabaseError("獲取公開困擾失敗", error)
// 轉換圖片格式
return (data || []).map((wish) => ({
...wish,
images: this.parseImages(wish.images),
}))
} catch (error) {
console.error("Error fetching public wishes:", error)
throw error
}
}
// 獲取所有困擾案例(用於分析,包含私密的)
static async getAllWishes(): Promise<Wish[]> {
try {
const { data, error } = await supabase
.from("wishes_with_likes")
.select("*")
.order("created_at", { ascending: false })
if (error) throw new SupabaseError("獲取所有困擾失敗", error)
// 轉換圖片格式
return (data || []).map((wish) => ({
...wish,
images: this.parseImages(wish.images),
}))
} catch (error) {
console.error("Error fetching all wishes:", error)
throw error
}
}
// 創建新的困擾案例(支持 Supabase Storage 圖片)
static async createWish(wishData: {
title: string
currentPain: string
expectedSolution: string
expectedEffect?: string
isPublic?: boolean
email?: string
images?: SupabaseImageFile[]
}): Promise<Wish> {
try {
// 準備圖片數據
const imageData =
wishData.images?.map((img) => ({
id: img.id,
name: img.name,
size: img.size,
type: img.type,
storage_path: img.storage_path,
public_url: img.public_url,
uploaded_at: img.uploaded_at,
})) || []
const insertData: WishInsert = {
title: wishData.title,
current_pain: wishData.currentPain,
expected_solution: wishData.expectedSolution,
expected_effect: wishData.expectedEffect || null,
is_public: wishData.isPublic ?? true,
email: wishData.email || null,
images: imageData,
}
const { data, error } = await supabase.from("wishes").insert(insertData).select().single()
if (error) throw new SupabaseError("創建困擾失敗", error)
return {
...data,
images: this.parseImages(data.images),
}
} catch (error) {
console.error("Error creating wish:", error)
throw error
}
}
// 解析圖片數據
private static parseImages(imagesData: any): SupabaseImageFile[] {
if (!imagesData || !Array.isArray(imagesData)) return []
return imagesData.map((img) => ({
id: img.id || `img_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
name: img.name || "unknown.jpg",
size: img.size || 0,
type: img.type || "image/jpeg",
storage_path: img.storage_path || "",
public_url: img.public_url || img.url || "", // 向後兼容
uploaded_at: img.uploaded_at || new Date().toISOString(),
}))
}
// 獲取統計數據
static async getWishesStats() {
try {
const { data, error } = await supabase.rpc("get_wishes_stats")
if (error) throw new SupabaseError("獲取統計數據失敗", error)
return data
} catch (error) {
console.error("Error fetching wishes stats:", error)
throw error
}
}
// 刪除困擾案例(包括相關圖片)
static async deleteWish(wishId: number): Promise<boolean> {
try {
// 先獲取困擾案例的圖片信息
const { data: wish, error: fetchError } = await supabase.from("wishes").select("images").eq("id", wishId).single()
if (fetchError) throw new SupabaseError("獲取困擾案例失敗", fetchError)
// 刪除相關圖片
if (wish.images && Array.isArray(wish.images)) {
const storagePaths = wish.images.map((img: any) => img.storage_path).filter((path: string) => path)
if (storagePaths.length > 0) {
await SupabaseImageService.deleteImages(storagePaths)
}
}
// 刪除困擾案例記錄
const { error: deleteError } = await supabase.from("wishes").delete().eq("id", wishId)
if (deleteError) throw new SupabaseError("刪除困擾案例失敗", deleteError)
return true
} catch (error) {
console.error("Error deleting wish:", error)
throw error
}
}
}
// 更新的數據遷移服務
export class MigrationService {
// 遷移 localStorage 中的困擾案例到 Supabase包括圖片遷移
static async migrateWishesFromLocalStorage(): Promise<{
success: number
failed: number
errors: string[]
}> {
const result = {
success: 0,
failed: 0,
errors: [] as string[],
}
try {
const localWishes = JSON.parse(localStorage.getItem("wishes") || "[]")
if (localWishes.length === 0) {
console.log("No local wishes to migrate")
return result
}
console.log(`Starting migration of ${localWishes.length} wishes...`)
for (const wish of localWishes) {
try {
let migratedImages: SupabaseImageFile[] = []
// 遷移圖片(如果有的話)
if (wish.images && Array.isArray(wish.images) && wish.images.length > 0) {
console.log(`Migrating ${wish.images.length} images for wish: ${wish.title}`)
const imageMigrationResult = await ImageMigrationService.migrateImagesFromWish(wish.images)
migratedImages = imageMigrationResult.successful
if (imageMigrationResult.failed.length > 0) {
console.warn(`Failed to migrate ${imageMigrationResult.failed.length} images for wish: ${wish.title}`)
// 記錄圖片遷移失敗,但不阻止整個 wish 的遷移
result.errors.push(`部分圖片遷移失敗 "${wish.title}": ${imageMigrationResult.failed.length} 張圖片`)
}
}
// 創建困擾案例
await WishService.createWish({
title: wish.title,
currentPain: wish.currentPain,
expectedSolution: wish.expectedSolution,
expectedEffect: wish.expectedEffect,
isPublic: wish.isPublic !== false,
email: wish.email,
images: migratedImages,
})
result.success++
console.log(`Successfully migrated wish: ${wish.title}`)
} catch (error) {
result.failed++
result.errors.push(`Failed to migrate wish "${wish.title}": ${error}`)
console.error(`Failed to migrate wish "${wish.title}":`, error)
}
}
console.log(`Migration completed: ${result.success} success, ${result.failed} failed`)
return result
} catch (error) {
console.error("Migration error:", error)
result.errors.push(`Migration process failed: ${error}`)
return result
}
}
// 清空 localStorage 中的舊數據
static clearLocalStorageData(): void {
const keysToRemove = ["wishes", "wishLikes", "userLikedWishes"]
keysToRemove.forEach((key) => {
localStorage.removeItem(key)
})
console.log("Local storage data cleared")
}
}
// 存儲健康檢查服務
export class StorageHealthService {
// 檢查 Supabase Storage 健康狀態
static async checkStorageHealth(): Promise<{
healthy: boolean
stats?: { totalFiles: number; totalSize: number }
error?: string
}> {
try {
const healthCheck = await SupabaseImageService.checkStorageHealth()
if (!healthCheck.healthy) {
return { healthy: false, error: healthCheck.error }
}
const stats = await SupabaseImageService.getStorageStats()
return {
healthy: true,
stats: {
totalFiles: stats.totalFiles,
totalSize: stats.totalSize,
},
error: stats.error,
}
} catch (error) {
return { healthy: false, error: `健康檢查失敗: ${error}` }
}
}
// 清理孤立的圖片
static async cleanupOrphanedImages(): Promise<{ cleaned: number; error?: string }> {
try {
const { data, error } = await supabase.rpc("cleanup_orphaned_images")
if (error) {
return { cleaned: 0, error: error.message }
}
return { cleaned: data || 0 }
} catch (error) {
return { cleaned: 0, error: `清理過程失敗: ${error}` }
}
}
}