fix topup
This commit is contained in:
parent
57c23e21bb
commit
89549e00ff
@ -1,20 +1,19 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { createServerSupabaseClient } from '@/lib/supabase-server'
|
||||
import { auth } from '@/lib/auth'
|
||||
import { getCreditStats } from '@/lib/services/credit'
|
||||
import { headers } from 'next/headers'
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const supabase = await createServerSupabaseClient()
|
||||
const { data: { user }, error: authError } = await supabase.auth.getUser()
|
||||
const session = await auth.api.getSession({
|
||||
headers: await headers()
|
||||
})
|
||||
|
||||
if (authError || !user) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Unauthorized' },
|
||||
{ status: 401 }
|
||||
)
|
||||
if (!session?.user) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const stats = await getCreditStats(user.id)
|
||||
const stats = await getCreditStats(session.user.id)
|
||||
|
||||
return NextResponse.json(stats)
|
||||
} catch (error) {
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { createServerSupabaseClient } from '@/lib/supabase-server'
|
||||
import { createOrGetStripeCustomer, createPaymentSession } from '@/lib/stripe'
|
||||
import { auth } from '@/lib/auth'
|
||||
import { createOrGetStripeCustomer, stripe } from '@/lib/stripe'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import { headers } from 'next/headers'
|
||||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
@ -22,74 +23,83 @@ export async function POST(request: Request) {
|
||||
)
|
||||
}
|
||||
|
||||
const supabase = await createServerSupabaseClient()
|
||||
// 获取Better Auth会话
|
||||
const session = await auth.api.getSession({
|
||||
headers: await headers()
|
||||
})
|
||||
|
||||
// 获取当前用户
|
||||
const { data: { user }, error: authError } = await supabase.auth.getUser()
|
||||
|
||||
if (authError || !user) {
|
||||
if (!session?.user) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
// 确保用户存在于数据库中,并获取用户信息
|
||||
const userData = await prisma.user.upsert({
|
||||
const user = session.user
|
||||
|
||||
// 获取或创建用户数据(不需要upsert,直接查找即可)
|
||||
let userData = await prisma.user.findUnique({
|
||||
where: { id: user.id },
|
||||
update: {
|
||||
email: user.email || '',
|
||||
updatedAt: new Date()
|
||||
},
|
||||
create: {
|
||||
id: user.id,
|
||||
email: user.email || '',
|
||||
name: user.user_metadata?.full_name || user.email?.split('@')[0] || 'User',
|
||||
emailVerified: false,
|
||||
username: user.user_metadata?.username || null,
|
||||
subscriptionPlanId: 'free',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
},
|
||||
select: {
|
||||
email: true,
|
||||
username: true
|
||||
name: true
|
||||
}
|
||||
})
|
||||
|
||||
try {
|
||||
// 如果用户不存在,创建用户(这种情况应该很少发生,因为用户应该已经通过sync API创建了)
|
||||
if (!userData) {
|
||||
userData = await prisma.user.create({
|
||||
data: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
emailVerified: user.emailVerified ?? false,
|
||||
subscriptionPlanId: 'free'
|
||||
},
|
||||
select: {
|
||||
email: true,
|
||||
name: true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 创建或获取 Stripe 客户
|
||||
const customer = await createOrGetStripeCustomer(
|
||||
const stripeCustomer = await createOrGetStripeCustomer(
|
||||
user.id,
|
||||
userData.email,
|
||||
userData.username || undefined
|
||||
userData.name || 'User'
|
||||
)
|
||||
|
||||
// 创建支付会话
|
||||
const session = await createPaymentSession(
|
||||
customer.id,
|
||||
Math.round(amount * 100), // 转换为美分
|
||||
`Credit top-up: $${amount}`,
|
||||
`${process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'}/credits/success?session_id={CHECKOUT_SESSION_ID}`,
|
||||
`${process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'}/credits/cancel`
|
||||
)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
sessionId: session.id,
|
||||
sessionUrl: session.url
|
||||
const checkoutSession = await stripe.checkout.sessions.create({
|
||||
customer: stripeCustomer.id,
|
||||
payment_method_types: ['card'],
|
||||
line_items: [
|
||||
{
|
||||
price_data: {
|
||||
currency: 'usd',
|
||||
product_data: {
|
||||
name: 'Credit Top-up',
|
||||
description: `Credit top-up for user ${user.id}`,
|
||||
},
|
||||
unit_amount: amount * 100, // 转换为美分
|
||||
},
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
mode: 'payment',
|
||||
success_url: `${process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000'}/credits/success?session_id={CHECKOUT_SESSION_ID}`,
|
||||
cancel_url: `${process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000'}/credits/cancel`,
|
||||
metadata: {
|
||||
type: 'credit_topup',
|
||||
amount: amount.toString(),
|
||||
userId: user.id // 添加userId到metadata用于验证
|
||||
},
|
||||
})
|
||||
|
||||
} catch (stripeError) {
|
||||
console.error('Stripe operation failed:', stripeError)
|
||||
return NextResponse.json({ url: checkoutSession.url })
|
||||
} catch (error) {
|
||||
console.error('Stripe top-up failed:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to create payment session' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Stripe top-up failed:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to process top-up request' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
@ -1,19 +1,20 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { createServerSupabaseClient } from '@/lib/supabase-server'
|
||||
import { auth } from '@/lib/auth'
|
||||
import { getCreditTransactions } from '@/lib/services/credit'
|
||||
import { headers } from 'next/headers'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const supabase = await createServerSupabaseClient()
|
||||
const { data: { user }, error: authError } = await supabase.auth.getUser()
|
||||
const session = await auth.api.getSession({
|
||||
headers: await headers()
|
||||
})
|
||||
|
||||
if (authError || !user) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Unauthorized' },
|
||||
{ status: 401 }
|
||||
)
|
||||
if (!session?.user) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const user = session.user
|
||||
|
||||
const { searchParams } = new URL(request.url)
|
||||
|
||||
// Parse query parameters
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { createServerSupabaseClient } from '@/lib/supabase-server'
|
||||
import { auth } from '@/lib/auth'
|
||||
import { stripe } from '@/lib/stripe'
|
||||
import { addCredit } from '@/lib/services/credit'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import { headers } from 'next/headers'
|
||||
|
||||
export async function GET(request: Request) {
|
||||
try {
|
||||
@ -16,106 +17,99 @@ export async function GET(request: Request) {
|
||||
)
|
||||
}
|
||||
|
||||
const supabase = await createServerSupabaseClient()
|
||||
// 获取Better Auth会话
|
||||
const session = await auth.api.getSession({
|
||||
headers: await headers()
|
||||
})
|
||||
|
||||
// 获取当前用户
|
||||
const { data: { user }, error: authError } = await supabase.auth.getUser()
|
||||
|
||||
if (authError || !user) {
|
||||
if (!session?.user) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const user = session.user
|
||||
|
||||
try {
|
||||
// 从 Stripe 获取支付会话信息
|
||||
const session = await stripe.checkout.sessions.retrieve(sessionId)
|
||||
const stripeSession = await stripe.checkout.sessions.retrieve(sessionId)
|
||||
|
||||
if (!session) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Session not found' },
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
|
||||
// 验证会话是否成功支付
|
||||
if (session.payment_status !== 'paid') {
|
||||
if (stripeSession.payment_status !== 'paid') {
|
||||
return NextResponse.json(
|
||||
{ error: 'Payment not completed' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// 获取金额
|
||||
const amount = session.metadata?.amount ? parseFloat(session.metadata.amount) : 0
|
||||
|
||||
if (amount <= 0) {
|
||||
// 验证支付会话属于当前用户
|
||||
if (stripeSession.metadata?.userId !== user.id) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid amount in session' },
|
||||
{ status: 400 }
|
||||
{ error: 'Payment session does not belong to current user' },
|
||||
{ status: 403 }
|
||||
)
|
||||
}
|
||||
|
||||
// 确保用户存在于数据库中
|
||||
await prisma.user.upsert({
|
||||
where: { id: user.id },
|
||||
update: {
|
||||
email: user.email || '',
|
||||
updatedAt: new Date()
|
||||
},
|
||||
create: {
|
||||
// 获取或创建用户数据
|
||||
let userData = await prisma.user.findUnique({
|
||||
where: { id: user.id }
|
||||
})
|
||||
|
||||
// 如果用户不存在,创建用户
|
||||
if (!userData) {
|
||||
userData = await prisma.user.create({
|
||||
data: {
|
||||
id: user.id,
|
||||
email: user.email || '',
|
||||
name: user.user_metadata?.full_name || user.email?.split('@')[0] || 'User',
|
||||
emailVerified: false,
|
||||
username: user.user_metadata?.username || null,
|
||||
subscriptionPlanId: 'free',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
emailVerified: user.emailVerified ?? false,
|
||||
subscriptionPlanId: 'free'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 检查是否已经处理过这个支付会话
|
||||
const existingCredit = await prisma.credit.findFirst({
|
||||
where: {
|
||||
referenceId: sessionId,
|
||||
referenceType: 'stripe_payment'
|
||||
referenceType: 'stripe_session'
|
||||
}
|
||||
})
|
||||
|
||||
if (existingCredit) {
|
||||
// 已经处理过,直接返回成功
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
amount,
|
||||
message: 'Payment already processed'
|
||||
message: 'Payment already processed',
|
||||
amount: existingCredit.amount,
|
||||
sessionId
|
||||
})
|
||||
}
|
||||
|
||||
// 添加信用额度
|
||||
const creditTransaction = await addCredit(
|
||||
// 从支付会话获取金额(Stripe金额以分为单位)
|
||||
const amount = (stripeSession.amount_total || 0) / 100
|
||||
|
||||
if (amount <= 0) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid payment amount' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// 添加信用记录
|
||||
await addCredit(
|
||||
user.id,
|
||||
amount,
|
||||
'user_purchase',
|
||||
`Stripe payment: $${amount}`
|
||||
`Stripe payment - Session: ${sessionId}`
|
||||
// 充值的信用不会过期,不设置过期时间
|
||||
)
|
||||
|
||||
// 更新 credit 记录的 referenceId 和 referenceType
|
||||
await prisma.credit.update({
|
||||
where: { id: creditTransaction.id },
|
||||
data: {
|
||||
referenceId: sessionId,
|
||||
referenceType: 'stripe_payment'
|
||||
}
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: 'Payment verified and credits added',
|
||||
amount,
|
||||
message: `Successfully added $${amount} to your account`,
|
||||
transaction: creditTransaction
|
||||
sessionId
|
||||
})
|
||||
|
||||
} catch (stripeError) {
|
||||
console.error('Stripe verification failed:', stripeError)
|
||||
console.error('Stripe error:', stripeError)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to verify payment with Stripe' },
|
||||
{ status: 500 }
|
||||
|
@ -1,18 +1,21 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { createServerSupabaseClient } from '@/lib/supabase-server'
|
||||
import { auth } from '@/lib/auth'
|
||||
import { getUserBalance } from '@/lib/services/credit'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import { headers } from 'next/headers'
|
||||
|
||||
// GET /api/users/credits - 获取用户信用额度概览(向后兼容)
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const supabase = await createServerSupabaseClient()
|
||||
const { data: { user }, error: authError } = await supabase.auth.getUser()
|
||||
const session = await auth.api.getSession({
|
||||
headers: await headers()
|
||||
})
|
||||
|
||||
if (authError || !user) {
|
||||
if (!session?.user) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const user = session.user
|
||||
const { searchParams } = new URL(request.url)
|
||||
const userId = searchParams.get('userId') || user.id
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { useAuthUser } from '@/hooks/useAuthUser'
|
||||
import { useBetterAuth } from '@/hooks/useBetterAuth'
|
||||
import { Header } from '@/components/layout/Header'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card } from '@/components/ui/card'
|
||||
@ -51,7 +51,7 @@ interface CreditTransactionsResult {
|
||||
}
|
||||
|
||||
export default function CreditsPage() {
|
||||
const { user, loading } = useAuthUser()
|
||||
const { user, loading } = useBetterAuth()
|
||||
const t = useTranslations('credits')
|
||||
|
||||
const [transactions, setTransactions] = useState<CreditTransaction[]>([])
|
||||
@ -206,10 +206,11 @@ export default function CreditsPage() {
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
if (response.ok && data.success && data.sessionUrl) {
|
||||
if (response.ok && data.url) {
|
||||
// 重定向到 Stripe Checkout
|
||||
window.location.href = data.sessionUrl
|
||||
window.location.href = data.url
|
||||
} else {
|
||||
console.error("failed to create payment session: ", data.error)
|
||||
alert(data.error || 'Failed to create payment session')
|
||||
}
|
||||
} catch (error) {
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { useAuthUser } from '@/hooks/useAuthUser'
|
||||
import { useBetterAuth } from '@/hooks/useBetterAuth'
|
||||
import { Header } from '@/components/layout/Header'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
@ -50,7 +50,7 @@ interface CreditInfo {
|
||||
}
|
||||
|
||||
export default function ProfilePage() {
|
||||
const { user, loading, triggerProfileUpdate } = useAuthUser()
|
||||
const { user, loading, triggerProfileUpdate } = useBetterAuth()
|
||||
const t = useTranslations('profile')
|
||||
const tCommon = useTranslations('common')
|
||||
const tAuth = useTranslations('auth')
|
||||
|
@ -1,10 +1,14 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState, useCallback, useRef } from 'react'
|
||||
import { useEffect, useState, useCallback } from 'react'
|
||||
import { useSession } from '@/lib/auth-client'
|
||||
import { userCache, UserData, SyncTrigger, SyncOptions, shouldSync } from '@/lib/user-cache'
|
||||
import type { User } from '@/lib/auth'
|
||||
|
||||
// 全局防抖变量
|
||||
const debounceTimeouts: Map<string, NodeJS.Timeout> = new Map()
|
||||
const pendingPromises: Map<string, Promise<void>> = new Map()
|
||||
|
||||
interface AuthUserState {
|
||||
user: User | null
|
||||
userData: UserData | null
|
||||
@ -12,20 +16,48 @@ interface AuthUserState {
|
||||
isAdmin: boolean
|
||||
}
|
||||
|
||||
export function useBetterAuth() {
|
||||
const [state, setState] = useState<AuthUserState>({
|
||||
// 全局状态管理,避免多个钩子实例重复调用
|
||||
let globalAuthState: AuthUserState = {
|
||||
user: null,
|
||||
userData: null,
|
||||
loading: true,
|
||||
isAdmin: false,
|
||||
})
|
||||
}
|
||||
const stateListeners: Set<(state: AuthUserState) => void> = new Set()
|
||||
let isGlobalInitialized = false
|
||||
let currentUserId: string | null = null
|
||||
let lastSyncTime: number = 0
|
||||
|
||||
// 广播状态变化给所有监听器
|
||||
const notifyStateChange = (newState: AuthUserState) => {
|
||||
globalAuthState = { ...newState }
|
||||
stateListeners.forEach(listener => listener(newState))
|
||||
}
|
||||
|
||||
export function useBetterAuth() {
|
||||
const [state, setState] = useState<AuthUserState>(globalAuthState)
|
||||
const { data: session, isPending } = useSession()
|
||||
const isInitializedRef = useRef(false)
|
||||
const currentUserIdRef = useRef<string | null>(null)
|
||||
|
||||
// 同步用户数据到数据库
|
||||
const syncUserToDatabase = useCallback(async (userId: string, options: SyncOptions) => {
|
||||
// 注册状态监听器
|
||||
useEffect(() => {
|
||||
const listener = (newState: AuthUserState) => {
|
||||
setState(newState)
|
||||
}
|
||||
stateListeners.add(listener)
|
||||
|
||||
return () => {
|
||||
stateListeners.delete(listener)
|
||||
}
|
||||
}, [])
|
||||
|
||||
// 全局同步函数,防止重复调用
|
||||
const globalSyncUserToDatabase = useCallback(async (userId: string, options: SyncOptions) => {
|
||||
// 检查是否在短时间内已经同步过
|
||||
const now = Date.now()
|
||||
if (!options.force && now - lastSyncTime < 5000) { // 5秒内不重复同步
|
||||
return
|
||||
}
|
||||
|
||||
if (!shouldSync(userId, options)) {
|
||||
return
|
||||
}
|
||||
@ -42,6 +74,7 @@ export function useBetterAuth() {
|
||||
}
|
||||
|
||||
userCache.recordSync(userId)
|
||||
lastSyncTime = Date.now()
|
||||
console.log(`User synced to database: ${userId} (trigger: ${options.trigger})`)
|
||||
} catch (error) {
|
||||
console.error('Failed to sync user to database:', error)
|
||||
@ -50,17 +83,18 @@ export function useBetterAuth() {
|
||||
})
|
||||
}, [])
|
||||
|
||||
// 获取用户数据
|
||||
const fetchUserData = useCallback(async (userId: string, options: SyncOptions) => {
|
||||
// 全局获取用户数据函数
|
||||
const globalFetchUserData = useCallback(async (userId: string, options: SyncOptions) => {
|
||||
// 先检查缓存
|
||||
if (!options.skipCache) {
|
||||
const cachedData = userCache.getUserData(userId)
|
||||
if (cachedData) {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
const newState = {
|
||||
...globalAuthState,
|
||||
userData: cachedData,
|
||||
isAdmin: cachedData.isAdmin || false,
|
||||
}))
|
||||
}
|
||||
notifyStateChange(newState)
|
||||
return cachedData
|
||||
}
|
||||
}
|
||||
@ -77,12 +111,13 @@ export function useBetterAuth() {
|
||||
// 更新缓存
|
||||
userCache.setUserData(userId, userData)
|
||||
|
||||
// 更新状态
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
// 更新全局状态
|
||||
const newState = {
|
||||
...globalAuthState,
|
||||
userData,
|
||||
isAdmin: userData.isAdmin || false,
|
||||
}))
|
||||
}
|
||||
notifyStateChange(newState)
|
||||
|
||||
return userData
|
||||
} catch (error) {
|
||||
@ -91,47 +126,98 @@ export function useBetterAuth() {
|
||||
}
|
||||
}, [])
|
||||
|
||||
// 处理用户状态变化
|
||||
const handleUserChange = useCallback(async (
|
||||
// 立即更新用户状态(不防抖)
|
||||
const updateUserState = useCallback((user: User | null) => {
|
||||
const newState = {
|
||||
...globalAuthState,
|
||||
user,
|
||||
loading: false,
|
||||
}
|
||||
notifyStateChange(newState)
|
||||
|
||||
// 如果用户变化了,清除旧用户的缓存
|
||||
if (currentUserId && currentUserId !== user?.id) {
|
||||
userCache.clearUserCache(currentUserId)
|
||||
lastSyncTime = 0 // 重置同步时间
|
||||
}
|
||||
currentUserId = user?.id || null
|
||||
|
||||
if (!user) {
|
||||
const userLoggedOutState = {
|
||||
...globalAuthState,
|
||||
user: null,
|
||||
userData: null,
|
||||
isAdmin: false,
|
||||
loading: false,
|
||||
}
|
||||
notifyStateChange(userLoggedOutState)
|
||||
}
|
||||
}, [])
|
||||
|
||||
// 全局处理用户数据同步(可以防抖)
|
||||
const globalHandleUserDataSync = useCallback(async (
|
||||
user: User | null,
|
||||
trigger: SyncTrigger = SyncTrigger.INITIAL_LOAD
|
||||
) => {
|
||||
// 更新用户状态
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
user,
|
||||
loading: false,
|
||||
}))
|
||||
if (!user) return
|
||||
|
||||
// 如果用户变化了,清除旧用户的缓存
|
||||
if (currentUserIdRef.current && currentUserIdRef.current !== user?.id) {
|
||||
userCache.clearUserCache(currentUserIdRef.current)
|
||||
}
|
||||
currentUserIdRef.current = user?.id || null
|
||||
|
||||
if (!user) {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
userData: null,
|
||||
isAdmin: false,
|
||||
}))
|
||||
// 避免重复处理相同用户
|
||||
if (trigger === SyncTrigger.INITIAL_LOAD && isGlobalInitialized && user.id === currentUserId) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const syncOptions: SyncOptions = { trigger }
|
||||
|
||||
// 并行执行同步和获取用户数据
|
||||
// 并行执行同步和获取用户数据,但添加去重机制
|
||||
await Promise.all([
|
||||
syncUserToDatabase(user.id, syncOptions),
|
||||
fetchUserData(user.id, syncOptions),
|
||||
globalSyncUserToDatabase(user.id, syncOptions),
|
||||
globalFetchUserData(user.id, syncOptions),
|
||||
])
|
||||
|
||||
console.log(`User data loaded: ${user.id} (trigger: ${trigger})`)
|
||||
} catch (error) {
|
||||
console.error('Failed to handle user change:', error)
|
||||
}
|
||||
}, [syncUserToDatabase, fetchUserData])
|
||||
}, [globalSyncUserToDatabase, globalFetchUserData])
|
||||
|
||||
// 创建防抖版本的用户数据同步处理
|
||||
const debouncedUserDataSync = useCallback(async (
|
||||
user: User | null,
|
||||
trigger: SyncTrigger = SyncTrigger.INITIAL_LOAD
|
||||
) => {
|
||||
const key = `${user?.id || 'anonymous'}-${trigger}`
|
||||
|
||||
// 清除之前的超时
|
||||
const existingTimeout = debounceTimeouts.get(key)
|
||||
if (existingTimeout) {
|
||||
clearTimeout(existingTimeout)
|
||||
}
|
||||
|
||||
// 如果有正在执行的Promise,直接返回
|
||||
const existingPromise = pendingPromises.get(key)
|
||||
if (existingPromise) {
|
||||
return existingPromise
|
||||
}
|
||||
|
||||
// 创建新的防抖Promise
|
||||
const debouncePromise = new Promise<void>((resolve) => {
|
||||
const timeout = setTimeout(async () => {
|
||||
debounceTimeouts.delete(key)
|
||||
try {
|
||||
await globalHandleUserDataSync(user, trigger)
|
||||
} finally {
|
||||
pendingPromises.delete(key)
|
||||
resolve()
|
||||
}
|
||||
}, 300)
|
||||
|
||||
debounceTimeouts.set(key, timeout)
|
||||
})
|
||||
|
||||
pendingPromises.set(key, debouncePromise)
|
||||
return debouncePromise
|
||||
}, [globalHandleUserDataSync])
|
||||
|
||||
// 监听 Better Auth 会话变化
|
||||
useEffect(() => {
|
||||
@ -141,12 +227,18 @@ export function useBetterAuth() {
|
||||
if (!mounted) return
|
||||
|
||||
const user = session?.user || null
|
||||
await handleUserChange(user,
|
||||
!isInitializedRef.current ? SyncTrigger.INITIAL_LOAD : SyncTrigger.SIGN_IN
|
||||
)
|
||||
const trigger = !isGlobalInitialized ? SyncTrigger.INITIAL_LOAD : SyncTrigger.SIGN_IN
|
||||
|
||||
if (!isInitializedRef.current) {
|
||||
isInitializedRef.current = true
|
||||
// 立即更新用户状态(不延迟)
|
||||
updateUserState(user)
|
||||
|
||||
// 异步进行数据同步(防抖延迟)
|
||||
if (user) {
|
||||
debouncedUserDataSync(user, trigger)
|
||||
}
|
||||
|
||||
if (!isGlobalInitialized) {
|
||||
isGlobalInitialized = true
|
||||
}
|
||||
}
|
||||
|
||||
@ -157,22 +249,23 @@ export function useBetterAuth() {
|
||||
return () => {
|
||||
mounted = false
|
||||
}
|
||||
}, [session, isPending, handleUserChange])
|
||||
}, [session, isPending, updateUserState, debouncedUserDataSync])
|
||||
|
||||
// 设置loading状态
|
||||
useEffect(() => {
|
||||
setState(prev => ({
|
||||
...prev,
|
||||
const newState = {
|
||||
...globalAuthState,
|
||||
loading: isPending
|
||||
}))
|
||||
}
|
||||
notifyStateChange(newState)
|
||||
}, [isPending])
|
||||
|
||||
// 手动刷新用户数据
|
||||
const refreshUserData = useCallback(async (force = false) => {
|
||||
if (!state.user) return
|
||||
if (!globalAuthState.user) return
|
||||
|
||||
try {
|
||||
setState(prev => ({ ...prev, loading: true }))
|
||||
notifyStateChange({ ...globalAuthState, loading: true })
|
||||
|
||||
const syncOptions: SyncOptions = {
|
||||
trigger: SyncTrigger.MANUAL_REFRESH,
|
||||
@ -181,15 +274,15 @@ export function useBetterAuth() {
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
syncUserToDatabase(state.user.id, syncOptions),
|
||||
fetchUserData(state.user.id, syncOptions),
|
||||
globalSyncUserToDatabase(globalAuthState.user.id, syncOptions),
|
||||
globalFetchUserData(globalAuthState.user.id, syncOptions),
|
||||
])
|
||||
} catch (error) {
|
||||
console.error('Failed to refresh user data:', error)
|
||||
} finally {
|
||||
setState(prev => ({ ...prev, loading: false }))
|
||||
notifyStateChange({ ...globalAuthState, loading: false })
|
||||
}
|
||||
}, [state.user, syncUserToDatabase, fetchUserData])
|
||||
}, [globalSyncUserToDatabase, globalFetchUserData])
|
||||
|
||||
// 登出
|
||||
const signOut = useCallback(async () => {
|
||||
@ -198,10 +291,15 @@ export function useBetterAuth() {
|
||||
await betterSignOut()
|
||||
|
||||
// 清除缓存
|
||||
if (currentUserIdRef.current) {
|
||||
userCache.clearUserCache(currentUserIdRef.current)
|
||||
if (currentUserId) {
|
||||
userCache.clearUserCache(currentUserId)
|
||||
}
|
||||
|
||||
// 重置全局状态
|
||||
isGlobalInitialized = false
|
||||
currentUserId = null
|
||||
lastSyncTime = 0
|
||||
|
||||
// 重定向到首页
|
||||
window.location.href = '/'
|
||||
} catch (error) {
|
||||
@ -211,40 +309,25 @@ export function useBetterAuth() {
|
||||
|
||||
// 触发用户信息更新(用于个人资料更新后)
|
||||
const triggerProfileUpdate = useCallback(async () => {
|
||||
if (!state.user) return
|
||||
|
||||
const syncOptions: SyncOptions = {
|
||||
trigger: SyncTrigger.PROFILE_UPDATE,
|
||||
force: true,
|
||||
skipCache: true,
|
||||
}
|
||||
if (!globalAuthState.user) return
|
||||
|
||||
try {
|
||||
await Promise.all([
|
||||
syncUserToDatabase(state.user.id, syncOptions),
|
||||
fetchUserData(state.user.id, syncOptions),
|
||||
])
|
||||
await globalHandleUserDataSync(globalAuthState.user, SyncTrigger.PROFILE_UPDATE)
|
||||
} catch (error) {
|
||||
console.error('Failed to update profile:', error)
|
||||
}
|
||||
}, [state.user, syncUserToDatabase, fetchUserData])
|
||||
}, [globalHandleUserDataSync])
|
||||
|
||||
// 触发订阅状态更新
|
||||
const triggerSubscriptionUpdate = useCallback(async () => {
|
||||
if (!state.user) return
|
||||
|
||||
const syncOptions: SyncOptions = {
|
||||
trigger: SyncTrigger.SUBSCRIPTION_CHANGE,
|
||||
force: true,
|
||||
skipCache: true,
|
||||
}
|
||||
if (!globalAuthState.user) return
|
||||
|
||||
try {
|
||||
await fetchUserData(state.user.id, syncOptions)
|
||||
await globalHandleUserDataSync(globalAuthState.user, SyncTrigger.SUBSCRIPTION_CHANGE)
|
||||
} catch (error) {
|
||||
console.error('Failed to update subscription:', error)
|
||||
}
|
||||
}, [state.user, fetchUserData])
|
||||
}, [globalHandleUserDataSync])
|
||||
|
||||
return {
|
||||
user: state.user,
|
||||
|
Loading…
Reference in New Issue
Block a user