-- Zellyy Finance 데이터베이스 스키마 스크립트 -- 작성일: 2023년 -- UUID 확장 활성화 CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -- 타임스탬프 함수 생성 (자동 업데이트용) CREATE OR REPLACE FUNCTION update_updated_at_column() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql; -- 1. 사용자 테이블 CREATE TABLE users ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), email TEXT UNIQUE NOT NULL, password TEXT NOT NULL, full_name TEXT NOT NULL, profile_image_url TEXT, monthly_income DECIMAL(12, 2), created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- 사용자 테이블 업데이트 트리거 CREATE TRIGGER update_users_updated_at BEFORE UPDATE ON users FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); -- 사용자 테이블 RLS 정책 ALTER TABLE users ENABLE ROW LEVEL SECURITY; CREATE POLICY "사용자는 자신의 정보만 볼 수 있음" ON users FOR SELECT USING (auth.uid() = id); CREATE POLICY "사용자는 자신의 정보만 수정할 수 있음" ON users FOR UPDATE USING (auth.uid() = id); -- 2. 카테고리 테이블 CREATE TABLE categories ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID REFERENCES users(id) ON DELETE CASCADE, name TEXT NOT NULL, color TEXT NOT NULL, icon TEXT, is_income BOOLEAN DEFAULT FALSE, is_default BOOLEAN DEFAULT FALSE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), UNIQUE(user_id, name) ); -- 카테고리 테이블 업데이트 트리거 CREATE TRIGGER update_categories_updated_at BEFORE UPDATE ON categories FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); -- 카테고리 테이블 RLS 정책 ALTER TABLE categories ENABLE ROW LEVEL SECURITY; CREATE POLICY "사용자는 자신의 카테고리와 기본 카테고리를 볼 수 있음" ON categories FOR SELECT USING (auth.uid() = user_id OR is_default = TRUE); CREATE POLICY "사용자는 자신의 카테고리만 수정할 수 있음" ON categories FOR UPDATE USING (auth.uid() = user_id AND is_default = FALSE); CREATE POLICY "사용자는 자신의 카테고리만 삭제할 수 있음" ON categories FOR DELETE USING (auth.uid() = user_id AND is_default = FALSE); CREATE POLICY "사용자는 자신의 카테고리만 생성할 수 있음" ON categories FOR INSERT WITH CHECK (auth.uid() = user_id); -- 3. 카드 테이블 CREATE TABLE cards ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, name TEXT NOT NULL, card_number TEXT, card_type TEXT NOT NULL, bank_name TEXT, color TEXT, billing_day INTEGER, payment_day INTEGER, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), UNIQUE(user_id, name) ); -- 카드 테이블 업데이트 트리거 CREATE TRIGGER update_cards_updated_at BEFORE UPDATE ON cards FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); -- 카드 테이블 RLS 정책 ALTER TABLE cards ENABLE ROW LEVEL SECURITY; CREATE POLICY "사용자는 자신의 카드만 볼 수 있음" ON cards FOR SELECT USING (auth.uid() = user_id); CREATE POLICY "사용자는 자신의 카드만 수정할 수 있음" ON cards FOR UPDATE USING (auth.uid() = user_id); CREATE POLICY "사용자는 자신의 카드만 삭제할 수 있음" ON cards FOR DELETE USING (auth.uid() = user_id); CREATE POLICY "사용자는 자신의 카드만 생성할 수 있음" ON cards FOR INSERT WITH CHECK (auth.uid() = user_id); -- 4. 한도 테이블 CREATE TABLE limits ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, card_id UUID REFERENCES cards(id) ON DELETE CASCADE, category_id UUID REFERENCES categories(id) ON DELETE SET NULL, amount DECIMAL(12, 2) NOT NULL, period TEXT NOT NULL, -- 'monthly', 'weekly', 'daily' start_date DATE NOT NULL, end_date DATE, is_active BOOLEAN DEFAULT TRUE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), CONSTRAINT card_or_category_required CHECK (card_id IS NOT NULL OR category_id IS NOT NULL) ); -- 한도 테이블 업데이트 트리거 CREATE TRIGGER update_limits_updated_at BEFORE UPDATE ON limits FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); -- 한도 테이블 RLS 정책 ALTER TABLE limits ENABLE ROW LEVEL SECURITY; CREATE POLICY "사용자는 자신의 한도만 볼 수 있음" ON limits FOR SELECT USING (auth.uid() = user_id); CREATE POLICY "사용자는 자신의 한도만 수정할 수 있음" ON limits FOR UPDATE USING (auth.uid() = user_id); CREATE POLICY "사용자는 자신의 한도만 삭제할 수 있음" ON limits FOR DELETE USING (auth.uid() = user_id); CREATE POLICY "사용자는 자신의 한도만 생성할 수 있음" ON limits FOR INSERT WITH CHECK (auth.uid() = user_id); -- 5. 지출 테이블 CREATE TABLE expenses ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, card_id UUID REFERENCES cards(id) ON DELETE SET NULL, category_id UUID REFERENCES categories(id) ON DELETE SET NULL, amount DECIMAL(12, 2) NOT NULL, description TEXT, date TIMESTAMP WITH TIME ZONE NOT NULL, location TEXT, receipt_image_url TEXT, is_income BOOLEAN DEFAULT FALSE, is_recurring BOOLEAN DEFAULT FALSE, recurring_period TEXT, -- 'monthly', 'weekly', 'daily' created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- 지출 테이블 업데이트 트리거 CREATE TRIGGER update_expenses_updated_at BEFORE UPDATE ON expenses FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); -- 지출 테이블 RLS 정책 ALTER TABLE expenses ENABLE ROW LEVEL SECURITY; CREATE POLICY "사용자는 자신의 지출만 볼 수 있음" ON expenses FOR SELECT USING (auth.uid() = user_id); CREATE POLICY "사용자는 자신의 지출만 수정할 수 있음" ON expenses FOR UPDATE USING (auth.uid() = user_id); CREATE POLICY "사용자는 자신의 지출만 삭제할 수 있음" ON expenses FOR DELETE USING (auth.uid() = user_id); CREATE POLICY "사용자는 자신의 지출만 생성할 수 있음" ON expenses FOR INSERT WITH CHECK (auth.uid() = user_id); -- 6. 템플릿 테이블 CREATE TABLE templates ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, name TEXT NOT NULL, card_id UUID REFERENCES cards(id) ON DELETE SET NULL, category_id UUID REFERENCES categories(id) ON DELETE SET NULL, amount DECIMAL(12, 2) NOT NULL, description TEXT, location TEXT, is_income BOOLEAN DEFAULT FALSE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), UNIQUE(user_id, name) ); -- 템플릿 테이블 업데이트 트리거 CREATE TRIGGER update_templates_updated_at BEFORE UPDATE ON templates FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); -- 템플릿 테이블 RLS 정책 ALTER TABLE templates ENABLE ROW LEVEL SECURITY; CREATE POLICY "사용자는 자신의 템플릿만 볼 수 있음" ON templates FOR SELECT USING (auth.uid() = user_id); CREATE POLICY "사용자는 자신의 템플릿만 수정할 수 있음" ON templates FOR UPDATE USING (auth.uid() = user_id); CREATE POLICY "사용자는 자신의 템플릿만 삭제할 수 있음" ON templates FOR DELETE USING (auth.uid() = user_id); CREATE POLICY "사용자는 자신의 템플릿만 생성할 수 있음" ON templates FOR INSERT WITH CHECK (auth.uid() = user_id); -- 7. 알림 테이블 CREATE TABLE notifications ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, title TEXT NOT NULL, message TEXT NOT NULL, type TEXT NOT NULL, -- 'limit_warning', 'payment_due', 'tip', 'system' related_id UUID, -- 관련된 항목의 ID (카드, 한도 등) is_read BOOLEAN DEFAULT FALSE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- 알림 테이블 업데이트 트리거 CREATE TRIGGER update_notifications_updated_at BEFORE UPDATE ON notifications FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); -- 알림 테이블 RLS 정책 ALTER TABLE notifications ENABLE ROW LEVEL SECURITY; CREATE POLICY "사용자는 자신의 알림만 볼 수 있음" ON notifications FOR SELECT USING (auth.uid() = user_id); CREATE POLICY "사용자는 자신의 알림만 수정할 수 있음" ON notifications FOR UPDATE USING (auth.uid() = user_id); CREATE POLICY "사용자는 자신의 알림만 삭제할 수 있음" ON notifications FOR DELETE USING (auth.uid() = user_id); CREATE POLICY "시스템만 알림을 생성할 수 있음" ON notifications FOR INSERT WITH CHECK (auth.uid() = user_id OR auth.uid() IS NULL); -- 8. 분석 설정 테이블 CREATE TABLE analysis_settings ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, budget_period TEXT DEFAULT 'monthly', -- 'monthly', 'weekly', 'daily' saving_goal DECIMAL(12, 2), analysis_period INTEGER DEFAULT 3, -- 분석에 사용할 과거 데이터 기간 (개월) created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), UNIQUE(user_id) ); -- 분석 설정 테이블 업데이트 트리거 CREATE TRIGGER update_analysis_settings_updated_at BEFORE UPDATE ON analysis_settings FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); -- 분석 설정 테이블 RLS 정책 ALTER TABLE analysis_settings ENABLE ROW LEVEL SECURITY; CREATE POLICY "사용자는 자신의 분석 설정만 볼 수 있음" ON analysis_settings FOR SELECT USING (auth.uid() = user_id); CREATE POLICY "사용자는 자신의 분석 설정만 수정할 수 있음" ON analysis_settings FOR UPDATE USING (auth.uid() = user_id); CREATE POLICY "사용자는 자신의 분석 설정만 삭제할 수 있음" ON analysis_settings FOR DELETE USING (auth.uid() = user_id); CREATE POLICY "사용자는 자신의 분석 설정만 생성할 수 있음" ON analysis_settings FOR INSERT WITH CHECK (auth.uid() = user_id); -- 기본 카테고리 데이터 삽입 INSERT INTO categories (name, color, icon, is_income, is_default) VALUES ('식비', '#FF5722', 'restaurant', FALSE, TRUE), ('교통', '#2196F3', 'directions_car', FALSE, TRUE), ('주거/통신', '#9C27B0', 'home', FALSE, TRUE), ('쇼핑', '#4CAF50', 'shopping_bag', FALSE, TRUE), ('의료/건강', '#F44336', 'local_hospital', FALSE, TRUE), ('문화/여가', '#FFEB3B', 'movie', FALSE, TRUE), ('교육', '#795548', 'school', FALSE, TRUE), ('경조사/기부', '#607D8B', 'card_giftcard', FALSE, TRUE), ('기타', '#9E9E9E', 'more_horiz', FALSE, TRUE), ('급여', '#4CAF50', 'payments', TRUE, TRUE), ('용돈', '#8BC34A', 'savings', TRUE, TRUE), ('상여금', '#CDDC39', 'card_giftcard', TRUE, TRUE), ('환불', '#FFC107', 'replay', TRUE, TRUE), ('투자수익', '#00BCD4', 'trending_up', TRUE, TRUE), ('기타수입', '#9E9E9E', 'more_horiz', TRUE, TRUE); -- 인덱스 생성 CREATE INDEX idx_expenses_user_id ON expenses(user_id); CREATE INDEX idx_expenses_date ON expenses(date); CREATE INDEX idx_expenses_category_id ON expenses(category_id); CREATE INDEX idx_expenses_card_id ON expenses(card_id); CREATE INDEX idx_limits_user_id ON limits(user_id); CREATE INDEX idx_cards_user_id ON cards(user_id); CREATE INDEX idx_categories_user_id ON categories(user_id); CREATE INDEX idx_notifications_user_id ON notifications(user_id); CREATE INDEX idx_notifications_is_read ON notifications(is_read);