CarboAny / src /lib /auth.ts
Esketch's picture
deploy: [P4] Strangler Pattern API Adapter Release (Orphan Clean Build v2)
daaf9d7
Raw
History Blame Contribute Delete
3.45 kB
import { supabase } from './supabase'
import type { Profile } from './database.types'
import { autoJoinActiveCampaigns } from './participants'
// --- Sign up / Sign in ---
export async function signInWithKakao() {
// Kakao's account_email scope requires business app verification (비즈 앱).
// For the pilot we skip it and only request nickname + profile image.
// Profile.email stays null for Kakao users; ensureProfile handles that.
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'kakao',
options: {
redirectTo: `${window.location.origin}/auth/callback`,
scopes: 'profile_nickname profile_image',
},
})
if (error) throw error
return data
}
// Ensure a profile row exists for the current user.
// Used after OAuth sign-in (Kakao) where signUpWithEmail's upsert path is skipped.
export async function ensureProfile(
userId: string,
fallbackEmail: string | null,
metadata: Record<string, unknown> = {}
): Promise<Profile> {
const { data: existing } = await supabase
.from('profiles')
.select('*')
.eq('id', userId)
.maybeSingle()
if (existing) return existing as Profile
const displayName =
(metadata.name as string) ||
(metadata.full_name as string) ||
(metadata.nickname as string) ||
(metadata.user_name as string) ||
(fallbackEmail ? fallbackEmail.split('@')[0] : '익명')
const profile: Profile = {
id: userId,
display_name: displayName,
email: fallbackEmail,
region: '제주',
role: 'citizen',
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
}
await upsertProfile(profile)
// Auto-join active campaigns for new users
autoJoinActiveCampaigns(userId).catch(console.error)
return profile
}
export async function signInWithEmail(email: string, password: string) {
const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
})
if (error) throw error
return data
}
export async function signUpWithEmail(
email: string,
password: string,
displayName: string
) {
const { data, error } = await supabase.auth.signUp({
email,
password,
options: {
data: { display_name: displayName },
},
})
if (error) throw error
// Create profile row + auto-join active campaigns
if (data.user) {
await upsertProfile({
id: data.user.id,
display_name: displayName,
email,
region: '제주',
role: 'citizen',
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
})
autoJoinActiveCampaigns(data.user.id).catch(console.error)
}
return data
}
export async function signOut() {
const { error } = await supabase.auth.signOut()
if (error) throw error
}
// --- Profile ---
export async function getProfile(): Promise<Profile | null> {
const {
data: { user },
} = await supabase.auth.getUser()
if (!user) return null
const { data, error } = await supabase
.from('profiles')
.select('*')
.eq('id', user.id)
.single()
if (error) return null
return data as Profile
}
async function upsertProfile(profile: Profile) {
const { error } = await supabase.from('profiles').upsert(profile)
if (error) throw error
}
// --- Auth state listener ---
export function onAuthStateChange(
callback: (event: string, session: unknown) => void
) {
return supabase.auth.onAuthStateChange(callback)
}