refactor: switch to fastapi backend
This commit is contained in:
+68
-116
@@ -1,149 +1,102 @@
|
||||
import { computed, ref } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import { supabase } from '@/lib/supabase'
|
||||
import type { User } from '@supabase/supabase-js'
|
||||
import {
|
||||
AUTH_EXPIRED_EVENT,
|
||||
AUTH_UPDATED_EVENT,
|
||||
apiRequest,
|
||||
clearAccessToken,
|
||||
refreshSession,
|
||||
setAccessToken,
|
||||
} from '@/lib/api'
|
||||
import type { AuthResponse, AuthUser } from '@/types/api'
|
||||
import type { Profile } from '@/types/database'
|
||||
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
const user = ref<User | null>(null)
|
||||
const user = ref<AuthUser | null>(null)
|
||||
const profile = ref<Profile | null>(null)
|
||||
const loading = ref(true)
|
||||
let listenersAttached = false
|
||||
|
||||
const isLoggedIn = computed(() => !!user.value)
|
||||
const isAdmin = computed(() => profile.value?.role === 'admin')
|
||||
|
||||
async function fetchProfile(userId?: string) {
|
||||
const id = userId ?? user.value?.id
|
||||
if (!id) return
|
||||
const { data } = await supabase
|
||||
.from('profiles')
|
||||
.select('*')
|
||||
.eq('id', id)
|
||||
.single()
|
||||
if (data) {
|
||||
profile.value = data as Profile
|
||||
}
|
||||
function applyAuth(auth: AuthResponse) {
|
||||
setAccessToken(auth.access_token)
|
||||
user.value = auth.user
|
||||
profile.value = auth.profile
|
||||
}
|
||||
|
||||
async function ensureUsernameAvailable(username: string, currentUserId?: string) {
|
||||
let query = supabase
|
||||
.from('profiles')
|
||||
.select('id')
|
||||
.eq('username', username)
|
||||
.limit(1)
|
||||
|
||||
if (currentUserId) {
|
||||
query = query.neq('id', currentUserId)
|
||||
}
|
||||
|
||||
const { data, error } = await query
|
||||
if (error) throw error
|
||||
if (data?.length) {
|
||||
throw new Error('这个昵称已经被使用,请换一个。')
|
||||
}
|
||||
}
|
||||
|
||||
async function login(email: string, password: string) {
|
||||
const { data, error } = await supabase.auth.signInWithPassword({ email, password })
|
||||
if (error) {
|
||||
if (error.message.includes('Invalid login credentials')) {
|
||||
throw new Error('邮箱或密码错误')
|
||||
}
|
||||
if (error.message.includes('Email not confirmed')) {
|
||||
throw new Error('邮箱未确认,请先查收确认邮件')
|
||||
}
|
||||
throw error
|
||||
}
|
||||
await fetchProfile(data.user.id)
|
||||
user.value = data.user
|
||||
}
|
||||
|
||||
async function register(email: string, password: string, username: string) {
|
||||
await ensureUsernameAvailable(username)
|
||||
|
||||
const { error } = await supabase.auth.signUp({
|
||||
email,
|
||||
password,
|
||||
options: {
|
||||
data: { username },
|
||||
emailRedirectTo: `${window.location.origin}/auth/confirm`,
|
||||
},
|
||||
})
|
||||
if (error) {
|
||||
if (error.message.includes('already registered')) {
|
||||
throw new Error('该邮箱已被注册')
|
||||
}
|
||||
if (error.message.includes('Password')) {
|
||||
throw new Error('密码强度不足,请使用至少6位密码')
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
const { error } = await supabase.auth.signOut()
|
||||
if (error) throw error
|
||||
function clearAuth() {
|
||||
clearAccessToken()
|
||||
user.value = null
|
||||
profile.value = null
|
||||
}
|
||||
|
||||
async function updateUsername(username: string) {
|
||||
if (!user.value) throw new Error('请先登录。')
|
||||
async function login(email: string, password: string) {
|
||||
const auth = await apiRequest<AuthResponse>('/auth/login', {
|
||||
method: 'POST',
|
||||
auth: false,
|
||||
body: { email, password },
|
||||
})
|
||||
applyAuth(auth)
|
||||
}
|
||||
|
||||
await ensureUsernameAvailable(username, user.value.id)
|
||||
async function register(email: string, password: string, username: string) {
|
||||
await apiRequest('/auth/register', {
|
||||
method: 'POST',
|
||||
auth: false,
|
||||
body: { email, password, username },
|
||||
})
|
||||
}
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('profiles')
|
||||
.update({ username })
|
||||
.eq('id', user.value.id)
|
||||
.select('*')
|
||||
.single()
|
||||
|
||||
if (error) {
|
||||
if (error.code === '23505') {
|
||||
throw new Error('这个昵称已经被使用,请换一个。')
|
||||
}
|
||||
throw error
|
||||
async function logout() {
|
||||
try {
|
||||
await apiRequest('/auth/logout', { method: 'POST', retry: false })
|
||||
} finally {
|
||||
clearAuth()
|
||||
}
|
||||
}
|
||||
|
||||
profile.value = data as Profile
|
||||
async function updateUsername(username: string) {
|
||||
const updatedProfile = await apiRequest<Profile>('/profiles/me', {
|
||||
method: 'PATCH',
|
||||
body: { username },
|
||||
})
|
||||
profile.value = updatedProfile
|
||||
}
|
||||
|
||||
async function updatePassword(password: string) {
|
||||
const { error } = await supabase.auth.updateUser({ password })
|
||||
if (error) throw error
|
||||
const auth = await apiRequest<AuthResponse>('/auth/password', {
|
||||
method: 'PATCH',
|
||||
body: { password },
|
||||
})
|
||||
applyAuth(auth)
|
||||
}
|
||||
|
||||
async function sendPasswordReset(email: string) {
|
||||
const { error } = await supabase.auth.resetPasswordForEmail(email, {
|
||||
redirectTo: `${window.location.origin}/auth/reset-password`,
|
||||
await apiRequest('/auth/forgot-password', {
|
||||
method: 'POST',
|
||||
auth: false,
|
||||
body: { email },
|
||||
})
|
||||
if (error) {
|
||||
if (error.message.includes('rate limit')) {
|
||||
throw new Error('发送过于频繁,请稍后再试。')
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async function initialize() {
|
||||
const { data: { session } } = await supabase.auth.getSession()
|
||||
const initialUser = session?.user ?? null
|
||||
if (initialUser) await fetchProfile(initialUser.id)
|
||||
user.value = initialUser
|
||||
loading.value = false
|
||||
if (!listenersAttached) {
|
||||
window.addEventListener(AUTH_UPDATED_EVENT, event => {
|
||||
applyAuth((event as CustomEvent<AuthResponse>).detail)
|
||||
})
|
||||
window.addEventListener(AUTH_EXPIRED_EVENT, clearAuth)
|
||||
listenersAttached = true
|
||||
}
|
||||
|
||||
supabase.auth.onAuthStateChange((_event, session) => {
|
||||
const nextUser = session?.user ?? null
|
||||
if (nextUser) {
|
||||
fetchProfile(nextUser.id).then(() => {
|
||||
user.value = nextUser
|
||||
})
|
||||
} else {
|
||||
user.value = null
|
||||
profile.value = null
|
||||
}
|
||||
})
|
||||
try {
|
||||
applyAuth(await refreshSession())
|
||||
} catch {
|
||||
clearAuth()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -152,7 +105,6 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
loading,
|
||||
isLoggedIn,
|
||||
isAdmin,
|
||||
fetchProfile,
|
||||
login,
|
||||
register,
|
||||
logout,
|
||||
|
||||
Reference in New Issue
Block a user