# Zellyy 데이터 모델 설계 이 문서는 Zellyy 프로젝트의 데이터베이스 모델 설계에 대한 상세 내용을 제공합니다. ## 스키마 구조 Zellyy 프로젝트는 Supabase의 PostgreSQL 데이터베이스를 사용하며, 모든 테이블은 `zellyy` 스키마 아래에 생성됩니다. 이는 기존 프로젝트와의 분리를 위한 것입니다. ## 핵심 테이블 ### 1. zellyy.users 사용자 정보를 저장하는 테이블입니다. Supabase Auth와 연동됩니다. ```sql CREATE TABLE zellyy.users ( id UUID REFERENCES auth.users(id) PRIMARY KEY, email TEXT UNIQUE NOT NULL, username TEXT UNIQUE, display_name TEXT, avatar_url TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), last_login TIMESTAMP WITH TIME ZONE, is_premium BOOLEAN DEFAULT FALSE, premium_until TIMESTAMP WITH TIME ZONE ); ``` ### 2. zellyy.cards 사용자가 작성한 카드 정보를 저장하는 테이블입니다. ```sql CREATE TABLE zellyy.cards ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID REFERENCES zellyy.users(id) NOT NULL, content TEXT NOT NULL, background_color TEXT DEFAULT '#FFFFFF', text_color TEXT DEFAULT '#000000', font_family TEXT DEFAULT 'system', font_size INTEGER DEFAULT 16, text_align TEXT DEFAULT 'center', is_public BOOLEAN DEFAULT FALSE, is_synced BOOLEAN DEFAULT FALSE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), deleted_at TIMESTAMP WITH TIME ZONE ); ``` ### 3. zellyy.card_tags 카드에 적용된 태그 정보를 저장하는 테이블입니다. ```sql CREATE TABLE zellyy.card_tags ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), card_id UUID REFERENCES zellyy.cards(id) NOT NULL, tag_name TEXT NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), UNIQUE(card_id, tag_name) ); ``` ### 4. zellyy.social_shares 카드의 소셜 미디어 공유 기록을 저장하는 테이블입니다. ```sql CREATE TABLE zellyy.social_shares ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), card_id UUID REFERENCES zellyy.cards(id) NOT NULL, user_id UUID REFERENCES zellyy.users(id) NOT NULL, platform TEXT NOT NULL, -- 'facebook', 'instagram', 'twitter', etc. share_url TEXT, shared_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), status TEXT DEFAULT 'pending', -- 'pending', 'success', 'failed' response_data JSONB ); ``` ### 5. zellyy.social_accounts 사용자의 소셜 미디어 계정 연동 정보를 저장하는 테이블입니다. ```sql CREATE TABLE zellyy.social_accounts ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID REFERENCES zellyy.users(id) NOT NULL, platform TEXT NOT NULL, -- 'facebook', 'instagram', 'twitter', etc. platform_user_id TEXT, access_token TEXT, refresh_token TEXT, token_expires_at TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), UNIQUE(user_id, platform) ); ``` ### 6. zellyy.subscriptions 사용자의 구독 정보를 저장하는 테이블입니다. ```sql CREATE TABLE zellyy.subscriptions ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID REFERENCES zellyy.users(id) NOT NULL, plan_type TEXT NOT NULL, -- 'monthly', 'yearly', etc. status TEXT NOT NULL, -- 'active', 'canceled', 'expired' start_date TIMESTAMP WITH TIME ZONE NOT NULL, end_date TIMESTAMP WITH TIME ZONE NOT NULL, payment_provider TEXT, -- 'apple', 'google', 'stripe', etc. payment_id TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); ``` ## 보안 정책 (RLS) Supabase의 Row Level Security(RLS)를 사용하여 데이터 접근을 제한합니다. ### zellyy.users 테이블 정책 ```sql -- 사용자는 자신의 정보만 읽을 수 있음 CREATE POLICY "사용자는 자신의 정보만 읽을 수 있음" ON zellyy.users FOR SELECT USING (auth.uid() = id); -- 사용자는 자신의 정보만 업데이트할 수 있음 CREATE POLICY "사용자는 자신의 정보만 업데이트할 수 있음" ON zellyy.users FOR UPDATE USING (auth.uid() = id); ``` ### zellyy.cards 테이블 정책 ```sql -- 사용자는 자신의 카드만 읽을 수 있음 (또는 공개 카드) CREATE POLICY "사용자는 자신의 카드만 읽을 수 있음" ON zellyy.cards FOR SELECT USING (auth.uid() = user_id OR is_public = TRUE); -- 사용자는 자신의 카드만 생성할 수 있음 CREATE POLICY "사용자는 자신의 카드만 생성할 수 있음" ON zellyy.cards FOR INSERT WITH CHECK (auth.uid() = user_id); -- 사용자는 자신의 카드만 업데이트할 수 있음 CREATE POLICY "사용자는 자신의 카드만 업데이트할 수 있음" ON zellyy.cards FOR UPDATE USING (auth.uid() = user_id); -- 사용자는 자신의 카드만 삭제할 수 있음 CREATE POLICY "사용자는 자신의 카드만 삭제할 수 있음" ON zellyy.cards FOR DELETE USING (auth.uid() = user_id); ``` ## 인덱스 성능 최적화를 위한 인덱스를 생성합니다. ```sql -- 카드 검색 최적화 CREATE INDEX idx_cards_user_id ON zellyy.cards(user_id); CREATE INDEX idx_cards_created_at ON zellyy.cards(created_at); CREATE INDEX idx_cards_is_public ON zellyy.cards(is_public); -- 태그 검색 최적화 CREATE INDEX idx_card_tags_card_id ON zellyy.card_tags(card_id); CREATE INDEX idx_card_tags_tag_name ON zellyy.card_tags(tag_name); -- 소셜 공유 검색 최적화 CREATE INDEX idx_social_shares_user_id ON zellyy.social_shares(user_id); CREATE INDEX idx_social_shares_card_id ON zellyy.social_shares(card_id); ``` ## 트리거 및 함수 자동화된 작업을 위한 트리거와 함수를 정의합니다. ### 업데이트 타임스탬프 트리거 ```sql -- 업데이트 타임스탬프 함수 CREATE OR REPLACE FUNCTION zellyy.update_timestamp() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql; -- 사용자 테이블에 트리거 적용 CREATE TRIGGER update_users_timestamp BEFORE UPDATE ON zellyy.users FOR EACH ROW EXECUTE FUNCTION zellyy.update_timestamp(); -- 카드 테이블에 트리거 적용 CREATE TRIGGER update_cards_timestamp BEFORE UPDATE ON zellyy.cards FOR EACH ROW EXECUTE FUNCTION zellyy.update_timestamp(); ``` ### 프리미엄 상태 업데이트 함수 ```sql -- 구독 상태에 따라 사용자의 프리미엄 상태 업데이트 CREATE OR REPLACE FUNCTION zellyy.update_premium_status() RETURNS TRIGGER AS $$ BEGIN IF NEW.status = 'active' THEN UPDATE zellyy.users SET is_premium = TRUE, premium_until = NEW.end_date WHERE id = NEW.user_id; ELSIF NEW.status IN ('canceled', 'expired') AND NEW.end_date <= NOW() THEN UPDATE zellyy.users SET is_premium = FALSE, premium_until = NULL WHERE id = NEW.user_id; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; -- 구독 테이블에 트리거 적용 CREATE TRIGGER update_premium_status_trigger AFTER INSERT OR UPDATE ON zellyy.subscriptions FOR EACH ROW EXECUTE FUNCTION zellyy.update_premium_status(); ``` ## 데이터 마이그레이션 및 초기 설정 프로젝트 초기 설정을 위한 SQL 스크립트입니다. ```sql -- 스키마 생성 CREATE SCHEMA IF NOT EXISTS zellyy; -- UUID 확장 활성화 CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -- 테이블 생성 (위에서 정의한 모든 테이블) -- 기본 관리자 사용자 생성 INSERT INTO zellyy.users (id, email, username, display_name, is_premium) VALUES ('00000000-0000-0000-0000-000000000000', 'admin@zellyy.com', 'admin', 'Zellyy Admin', TRUE); ``` ## 확장성 고려사항 1. **샤딩**: 사용자 수가 크게 증가할 경우, 사용자 ID를 기준으로 데이터를 샤딩하는 전략을 고려할 수 있습니다. 2. **아카이빙**: 오래된 카드 데이터는 별도의 아카이브 테이블로 이동하여 주 테이블의 성능을 유지할 수 있습니다. 3. **캐싱**: 자주 접근하는 데이터(예: 공개 카드, 인기 태그)는 Redis와 같은 캐시 시스템을 도입하여 성능을 향상시킬 수 있습니다. 4. **읽기 복제본**: 쿼리 부하가 높아질 경우, 읽기 전용 복제본을 설정하여 부하를 분산할 수 있습니다. ## 데이터 백업 전략 1. **정기 백업**: 매일 전체 데이터베이스 백업을 수행합니다. 2. **증분 백업**: 시간별 WAL(Write-Ahead Log) 백업을 통해 세부적인 복구 지점을 제공합니다. 3. **지리적 복제**: 재해 복구를 위해 다른 지역에 백업을 저장합니다. ## 결론 이 데이터 모델은 Zellyy 프로젝트의 초기 요구사항을 충족하도록 설계되었습니다. 사용자 피드백과 실제 사용 패턴에 따라 모델을 지속적으로 개선하고 확장할 계획입니다.