초기 커밋

This commit is contained in:
hansoo
2025-03-26 18:16:46 +09:00
commit 266674cc0e
67 changed files with 14235 additions and 0 deletions

View File

@@ -0,0 +1,265 @@
# 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 프로젝트의 초기 요구사항을 충족하도록 설계되었습니다. 사용자 피드백과 실제 사용 패턴에 따라 모델을 지속적으로 개선하고 확장할 계획입니다.