diff --git a/src/components/auth/LoginForm.tsx b/src/components/auth/LoginForm.tsx index 3b357d0..19b061e 100644 --- a/src/components/auth/LoginForm.tsx +++ b/src/components/auth/LoginForm.tsx @@ -4,7 +4,7 @@ import { Link } from "react-router-dom"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; -import { ArrowRight, Mail, KeyRound, Eye, EyeOff, AlertTriangle } from "lucide-react"; +import { ArrowRight, Mail, KeyRound, Eye, EyeOff, AlertTriangle, Loader2 } from "lucide-react"; interface LoginFormProps { email: string; @@ -14,6 +14,7 @@ interface LoginFormProps { showPassword: boolean; setShowPassword: (show: boolean) => void; isLoading: boolean; + isSettingUpTables?: boolean; loginError: string | null; handleLogin: (e: React.FormEvent) => Promise; } @@ -26,6 +27,7 @@ const LoginForm: React.FC = ({ showPassword, setShowPassword, isLoading, + isSettingUpTables = false, loginError, handleLogin }) => { @@ -102,11 +104,14 @@ const LoginForm: React.FC = ({ diff --git a/src/hooks/useLogin.ts b/src/hooks/useLogin.ts index 67c7195..be4f22b 100644 --- a/src/hooks/useLogin.ts +++ b/src/hooks/useLogin.ts @@ -3,6 +3,7 @@ import { useState } from "react"; import { useNavigate } from "react-router-dom"; import { useToast } from "@/hooks/useToast.wrapper"; import { useAuth } from "@/contexts/auth"; +import { createRequiredTables } from "@/lib/supabase/setup"; export function useLogin() { const [email, setEmail] = useState(""); @@ -10,6 +11,7 @@ export function useLogin() { const [showPassword, setShowPassword] = useState(false); const [isLoading, setIsLoading] = useState(false); const [loginError, setLoginError] = useState(null); + const [isSettingUpTables, setIsSettingUpTables] = useState(false); const navigate = useNavigate(); const { toast } = useToast(); const { signIn } = useAuth(); @@ -30,7 +32,7 @@ export function useLogin() { setIsLoading(true); try { - const { error } = await signIn(email, password); + const { error, user } = await signIn(email, password); if (error) { console.error("로그인 실패:", error); @@ -61,6 +63,23 @@ export function useLogin() { variant: "destructive" }); } else { + // 로그인 성공 시 필요한 테이블 설정 + try { + setIsSettingUpTables(true); + const { success, message } = await createRequiredTables(); + + if (success) { + console.log("테이블 설정 성공:", message); + } else { + console.warn("테이블 설정 문제:", message); + // 테이블 설정 실패해도 로그인은 진행 + } + } catch (setupError) { + console.error("테이블 설정 중 오류:", setupError); + } finally { + setIsSettingUpTables(false); + } + navigate("/"); } } catch (err: any) { @@ -86,6 +105,7 @@ export function useLogin() { showPassword, setShowPassword, isLoading, + isSettingUpTables, loginError, setLoginError, handleLogin diff --git a/src/lib/supabase/setup.ts b/src/lib/supabase/setup.ts new file mode 100644 index 0000000..da82d41 --- /dev/null +++ b/src/lib/supabase/setup.ts @@ -0,0 +1,224 @@ + +import { supabase } from './client'; + +/** + * Supabase 데이터베이스에 필요한 테이블을 생성합니다. + * 이 함수는 로그인 후 실행되어야 합니다. + */ +export const createRequiredTables = async (): Promise<{ success: boolean; message: string }> => { + try { + console.log('데이터베이스 테이블 생성 시작...'); + + // 테이블이 이미 존재하는지 확인 + const { data: existingTables, error: tableError } = await supabase + .rpc('get_tables'); + + if (tableError) { + console.error('테이블 목록 가져오기 실패:', tableError); + + // RPC가 존재하지 않는 경우, 다른 방법으로 테이블 생성 시도 + return await createTablesWithRawSQL(); + } + + console.log('기존 테이블:', existingTables); + + // 이미 테이블이 존재하는 경우 + if (Array.isArray(existingTables) && + existingTables.some(t => t.name === 'transactions') && + existingTables.some(t => t.name === 'budgets')) { + return { + success: true, + message: '필요한 테이블이 이미 존재합니다.' + }; + } + + return await createTablesWithRawSQL(); + + } catch (error: any) { + console.error('테이블 생성 중 오류 발생:', error); + return { + success: false, + message: `테이블 생성 실패: ${error.message || '알 수 없는 오류'}` + }; + } +}; + +/** + * SQL 쿼리를 직접 실행하여 테이블을 생성합니다. + */ +const createTablesWithRawSQL = async (): Promise<{ success: boolean; message: string }> => { + try { + // transactions 테이블 생성 + const { error: transactionsError } = await supabase.rpc('execute_sql', { + sql_query: ` + CREATE TABLE IF NOT EXISTS transactions ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id UUID REFERENCES auth.users(id) NOT NULL, + title TEXT NOT NULL, + amount NUMERIC NOT NULL, + category TEXT NOT NULL, + date TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + type TEXT NOT NULL, + notes TEXT, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() + ); + + -- Row Level Security 설정 + ALTER TABLE transactions ENABLE ROW LEVEL SECURITY; + + -- 사용자 정책 설정 (읽기) + CREATE POLICY "사용자는 자신의 트랜잭션만 볼 수 있음" + ON transactions FOR SELECT + USING (auth.uid() = user_id); + + -- 사용자 정책 설정 (쓰기) + CREATE POLICY "사용자는 자신의 트랜잭션만 추가할 수 있음" + ON transactions FOR INSERT + WITH CHECK (auth.uid() = user_id); + + -- 사용자 정책 설정 (업데이트) + CREATE POLICY "사용자는 자신의 트랜잭션만 업데이트할 수 있음" + ON transactions FOR UPDATE + USING (auth.uid() = user_id); + + -- 사용자 정책 설정 (삭제) + CREATE POLICY "사용자는 자신의 트랜잭션만 삭제할 수 있음" + ON transactions FOR DELETE + USING (auth.uid() = user_id); + ` + }); + + if (transactionsError) { + console.error('transactions 테이블 생성 실패:', transactionsError); + return { + success: false, + message: `transactions 테이블 생성 실패: ${transactionsError.message}` + }; + } + + // budgets 테이블 생성 + const { error: budgetsError } = await supabase.rpc('execute_sql', { + sql_query: ` + CREATE TABLE IF NOT EXISTS budgets ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id UUID REFERENCES auth.users(id) NOT NULL, + month INTEGER NOT NULL, + year INTEGER NOT NULL, + total_budget NUMERIC NOT NULL DEFAULT 0, + categories JSONB NOT NULL DEFAULT '{}', + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + UNIQUE (user_id, month, year) + ); + + -- Row Level Security 설정 + ALTER TABLE budgets ENABLE ROW LEVEL SECURITY; + + -- 사용자 정책 설정 (읽기) + CREATE POLICY "사용자는 자신의 예산만 볼 수 있음" + ON budgets FOR SELECT + USING (auth.uid() = user_id); + + -- 사용자 정책 설정 (쓰기) + CREATE POLICY "사용자는 자신의 예산만 추가할 수 있음" + ON budgets FOR INSERT + WITH CHECK (auth.uid() = user_id); + + -- 사용자 정책 설정 (업데이트) + CREATE POLICY "사용자는 자신의 예산만 업데이트할 수 있음" + ON budgets FOR UPDATE + USING (auth.uid() = user_id); + + -- 사용자 정책 설정 (삭제) + CREATE POLICY "사용자는 자신의 예산만 삭제할 수 있음" + ON budgets FOR DELETE + USING (auth.uid() = user_id); + ` + }); + + if (budgetsError) { + console.error('budgets 테이블 생성 실패:', budgetsError); + return { + success: false, + message: `budgets 테이블 생성 실패: ${budgetsError.message}` + }; + } + + // _tests 테이블 생성 (연결 테스트용) + const { error: testsError } = await supabase.rpc('execute_sql', { + sql_query: ` + CREATE TABLE IF NOT EXISTS _tests ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + test_name TEXT NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() + ); + + -- 모든 사용자가 접근 가능하도록 설정 + ALTER TABLE _tests ENABLE ROW LEVEL SECURITY; + CREATE POLICY "모든 사용자가 테스트 테이블에 접근 가능" + ON _tests FOR SELECT + USING (true); + ` + }); + + if (testsError) { + console.error('_tests 테이블 생성 실패:', testsError); + // _tests 테이블은 필수가 아니므로 오류가 발생해도 진행 + } + + return { + success: true, + message: '모든 필수 테이블이 성공적으로 생성되었습니다.' + }; + } catch (error: any) { + console.error('테이블 생성 중 오류 발생:', error); + return { + success: false, + message: `테이블 생성 실패: ${error.message || '알 수 없는 오류'}` + }; + } +}; + +/** + * 테이블 상태를 확인합니다. + */ +export const checkTablesStatus = async (): Promise<{ + transactions: boolean; + budgets: boolean; + tests: boolean; +}> => { + const tables = { + transactions: false, + budgets: false, + tests: false + }; + + try { + // transactions 테이블 확인 + const { count: transactionsCount, error: transactionsError } = await supabase + .from('transactions') + .select('*', { count: 'exact', head: true }); + + tables.transactions = !transactionsError; + + // budgets 테이블 확인 + const { count: budgetsCount, error: budgetsError } = await supabase + .from('budgets') + .select('*', { count: 'exact', head: true }); + + tables.budgets = !budgetsError; + + // _tests 테이블 확인 + const { count: testsCount, error: testsError } = await supabase + .from('_tests') + .select('*', { count: 'exact', head: true }); + + tables.tests = !testsError; + + return tables; + } catch (error) { + console.error('테이블 상태 확인 중 오류 발생:', error); + return tables; + } +};