dramaling-vocab-learning/docs/03_development/database-schema-detailed.md

465 lines
13 KiB
Markdown

# DramaLing 資料庫 Schema 詳細設計
## 🗄️ 資料庫架構
### 技術棧
- **Database**: PostgreSQL 15+ (via Supabase)
- **ORM**: Prisma
- **Migration Tool**: Prisma Migrate
- **Backup**: Daily automated backups via Supabase
## 📦 資料表設計
### 1. 用戶相關表
#### users
```sql
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) UNIQUE NOT NULL,
username VARCHAR(50) UNIQUE NOT NULL,
password_hash VARCHAR(255), -- NULL for OAuth users
provider VARCHAR(20) DEFAULT 'email', -- email, google, facebook
provider_id VARCHAR(255),
email_verified BOOLEAN DEFAULT FALSE,
avatar_url TEXT,
display_name VARCHAR(100),
bio TEXT,
preferred_language VARCHAR(10) DEFAULT 'zh-TW',
timezone VARCHAR(50) DEFAULT 'Asia/Taipei',
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
last_login_at TIMESTAMP WITH TIME ZONE,
is_active BOOLEAN DEFAULT TRUE,
is_premium BOOLEAN DEFAULT FALSE,
premium_expires_at TIMESTAMP WITH TIME ZONE,
-- Indexes
INDEX idx_users_email (email),
INDEX idx_users_username (username),
INDEX idx_users_provider (provider, provider_id)
);
```
#### user_profiles
```sql
CREATE TABLE user_profiles (
user_id UUID PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
daily_goal INTEGER DEFAULT 10,
reminder_enabled BOOLEAN DEFAULT TRUE,
reminder_time TIME DEFAULT '20:00:00',
study_streak INTEGER DEFAULT 0,
longest_streak INTEGER DEFAULT 0,
total_study_time INTEGER DEFAULT 0, -- in minutes
total_words_learned INTEGER DEFAULT 0,
experience_points INTEGER DEFAULT 0,
level INTEGER DEFAULT 1,
achievements JSONB DEFAULT '[]',
preferences JSONB DEFAULT '{}',
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
```
#### sessions
```sql
CREATE TABLE sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
token VARCHAR(255) UNIQUE NOT NULL,
refresh_token VARCHAR(255) UNIQUE,
device_info JSONB,
ip_address INET,
user_agent TEXT,
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
last_activity TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
INDEX idx_sessions_token (token),
INDEX idx_sessions_user (user_id),
INDEX idx_sessions_expires (expires_at)
);
```
### 2. 詞卡相關表
#### flashcard_sets
```sql
CREATE TABLE flashcard_sets (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
name VARCHAR(200) NOT NULL,
description TEXT,
cover_image_url TEXT,
color VARCHAR(20) DEFAULT '#3B82F6',
is_public BOOLEAN DEFAULT FALSE,
is_system BOOLEAN DEFAULT FALSE,
tags TEXT[] DEFAULT '{}',
source_type VARCHAR(50), -- 'manual', 'ai_generated', 'imported'
source_content TEXT, -- Original text for AI generated sets
card_count INTEGER DEFAULT 0,
completed_count INTEGER DEFAULT 0,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
last_studied_at TIMESTAMP WITH TIME ZONE,
INDEX idx_sets_user (user_id),
INDEX idx_sets_public (is_public),
INDEX idx_sets_tags (tags) USING GIN
);
```
#### flashcards
```sql
CREATE TABLE flashcards (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
set_id UUID NOT NULL REFERENCES flashcard_sets(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
-- Core content
word VARCHAR(200) NOT NULL,
part_of_speech VARCHAR(50),
pronunciation VARCHAR(200),
audio_url TEXT,
-- Translations
translation VARCHAR(500) NOT NULL,
definition TEXT,
definition_cn TEXT,
-- Examples
example_sentence TEXT,
example_translation TEXT,
additional_examples JSONB DEFAULT '[]',
-- Learning aids
synonyms TEXT[] DEFAULT '{}',
antonyms TEXT[] DEFAULT '{}',
related_words TEXT[] DEFAULT '{}',
memory_tips TEXT,
notes TEXT,
image_url TEXT,
-- Metadata
difficulty_level VARCHAR(20) DEFAULT 'intermediate',
frequency_rank INTEGER,
tags TEXT[] DEFAULT '{}',
context_tags TEXT[] DEFAULT '{}', -- 'business', 'casual', 'academic', etc.
-- Learning data
is_favorite BOOLEAN DEFAULT FALSE,
is_mastered BOOLEAN DEFAULT FALSE,
mastery_level INTEGER DEFAULT 0, -- 0-100
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
INDEX idx_cards_set (set_id),
INDEX idx_cards_user (user_id),
INDEX idx_cards_word (word),
INDEX idx_cards_mastery (mastery_level),
INDEX idx_cards_tags (tags) USING GIN,
UNIQUE(set_id, word)
);
```
### 3. 學習系統表
#### learning_records
```sql
CREATE TABLE learning_records (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
flashcard_id UUID NOT NULL REFERENCES flashcards(id) ON DELETE CASCADE,
-- SM-2 Algorithm fields
ease_factor DECIMAL(3,2) DEFAULT 2.5,
interval INTEGER DEFAULT 1, -- days
repetitions INTEGER DEFAULT 0,
-- Review data
last_reviewed_at TIMESTAMP WITH TIME ZONE,
next_review_at TIMESTAMP WITH TIME ZONE,
-- Performance tracking
total_reviews INTEGER DEFAULT 0,
correct_reviews INTEGER DEFAULT 0,
incorrect_reviews INTEGER DEFAULT 0,
average_rating DECIMAL(2,1),
-- Time tracking
total_study_time INTEGER DEFAULT 0, -- seconds
last_study_duration INTEGER DEFAULT 0,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
INDEX idx_learning_user (user_id),
INDEX idx_learning_card (flashcard_id),
INDEX idx_learning_next_review (next_review_at),
UNIQUE(user_id, flashcard_id)
);
```
#### review_logs
```sql
CREATE TABLE review_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
flashcard_id UUID NOT NULL REFERENCES flashcards(id) ON DELETE CASCADE,
learning_record_id UUID REFERENCES learning_records(id) ON DELETE CASCADE,
-- Review details
rating INTEGER NOT NULL CHECK (rating >= 1 AND rating <= 5),
response_time INTEGER, -- milliseconds
review_type VARCHAR(20), -- 'scheduled', 'practice', 'cram'
mode VARCHAR(20), -- 'flip', 'quiz', 'typing'
-- Context
session_id UUID,
device_type VARCHAR(20),
reviewed_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
INDEX idx_review_user (user_id),
INDEX idx_review_card (flashcard_id),
INDEX idx_review_date (reviewed_at)
);
```
#### study_sessions
```sql
CREATE TABLE study_sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
-- Session info
start_time TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
end_time TIMESTAMP WITH TIME ZONE,
duration INTEGER, -- seconds
-- Performance
cards_studied INTEGER DEFAULT 0,
cards_mastered INTEGER DEFAULT 0,
correct_count INTEGER DEFAULT 0,
incorrect_count INTEGER DEFAULT 0,
accuracy_rate DECIMAL(5,2),
-- Context
study_mode VARCHAR(20), -- 'review', 'learn', 'cram'
set_id UUID REFERENCES flashcard_sets(id) ON DELETE SET NULL,
device_type VARCHAR(20),
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
INDEX idx_sessions_user_date (user_id, start_time)
);
```
### 4. AI 生成相關表
#### ai_generation_logs
```sql
CREATE TABLE ai_generation_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
-- Request info
input_text TEXT,
input_type VARCHAR(50), -- 'text', 'theme'
theme VARCHAR(100),
difficulty_level VARCHAR(20),
card_count INTEGER,
-- Generation details
model_used VARCHAR(50),
prompt_tokens INTEGER,
completion_tokens INTEGER,
total_tokens INTEGER,
generation_time INTEGER, -- milliseconds
-- Results
generated_cards JSONB,
cards_saved INTEGER,
set_id UUID REFERENCES flashcard_sets(id) ON DELETE SET NULL,
-- Status
status VARCHAR(20), -- 'pending', 'completed', 'failed'
error_message TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
INDEX idx_ai_gen_user (user_id),
INDEX idx_ai_gen_date (created_at)
);
```
### 5. 統計與分析表
#### daily_stats
```sql
CREATE TABLE daily_stats (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
date DATE NOT NULL,
-- Learning metrics
cards_reviewed INTEGER DEFAULT 0,
cards_learned INTEGER DEFAULT 0,
cards_mastered INTEGER DEFAULT 0,
study_time INTEGER DEFAULT 0, -- minutes
sessions_count INTEGER DEFAULT 0,
-- Performance
average_accuracy DECIMAL(5,2),
average_ease_factor DECIMAL(3,2),
-- Streaks
streak_maintained BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
UNIQUE(user_id, date),
INDEX idx_daily_stats_user_date (user_id, date)
);
```
#### achievements
```sql
CREATE TABLE achievements (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(100) UNIQUE NOT NULL,
description TEXT,
icon_url TEXT,
category VARCHAR(50),
points INTEGER DEFAULT 0,
requirement_type VARCHAR(50), -- 'streak', 'total_words', 'accuracy', etc.
requirement_value INTEGER,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
```
#### user_achievements
```sql
CREATE TABLE user_achievements (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
achievement_id UUID NOT NULL REFERENCES achievements(id) ON DELETE CASCADE,
unlocked_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
progress INTEGER DEFAULT 0,
UNIQUE(user_id, achievement_id),
INDEX idx_user_achievements (user_id)
);
```
### 6. 通知與設定表
#### notifications
```sql
CREATE TABLE notifications (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
type VARCHAR(50) NOT NULL, -- 'reminder', 'achievement', 'streak', etc.
title VARCHAR(200) NOT NULL,
message TEXT,
data JSONB,
is_read BOOLEAN DEFAULT FALSE,
read_at TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP WITH TIME ZONE,
INDEX idx_notifications_user (user_id),
INDEX idx_notifications_unread (user_id, is_read)
);
```
#### user_settings
```sql
CREATE TABLE user_settings (
user_id UUID PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
-- Notification settings
email_notifications BOOLEAN DEFAULT TRUE,
push_notifications BOOLEAN DEFAULT TRUE,
reminder_notifications BOOLEAN DEFAULT TRUE,
achievement_notifications BOOLEAN DEFAULT TRUE,
-- Learning preferences
default_study_mode VARCHAR(20) DEFAULT 'flip',
auto_play_audio BOOLEAN DEFAULT TRUE,
show_pronunciation BOOLEAN DEFAULT TRUE,
review_order VARCHAR(20) DEFAULT 'due_date', -- 'due_date', 'random', 'difficulty'
-- Display preferences
theme VARCHAR(20) DEFAULT 'light',
font_size VARCHAR(20) DEFAULT 'medium',
card_layout VARCHAR(20) DEFAULT 'standard',
-- Privacy
profile_visibility VARCHAR(20) DEFAULT 'private',
share_progress BOOLEAN DEFAULT FALSE,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
```
## 🔗 關聯關係
### 主要關係
1. **users → flashcard_sets**: 一對多 (用戶擁有多個卡組)
2. **flashcard_sets → flashcards**: 一對多 (卡組包含多個詞卡)
3. **users → learning_records**: 一對多 (用戶的學習記錄)
4. **flashcards → learning_records**: 一對多 (每個詞卡的學習記錄)
5. **learning_records → review_logs**: 一對多 (學習記錄的詳細日誌)
## 🔑 索引策略
### 主要索引
1. **用戶查詢**: email, username
2. **詞卡查詢**: word, set_id, user_id
3. **學習查詢**: next_review_at, user_id
4. **統計查詢**: user_id + date
5. **全文搜索**: 使用 PostgreSQL 的 GIN 索引於 tags 和 JSONB 欄位
## 🔄 資料遷移策略
### 版本控制
```bash
# Prisma migration commands
npx prisma migrate dev --name init
npx prisma migrate deploy
npx prisma migrate status
```
### 備份策略
1. **自動備份**: 每日 02:00 UTC
2. **保留期限**: 30 天
3. **地理位置**: 跨區域複製
4. **恢復測試**: 每季度一次
## 📊 效能優化
### 查詢優化
1. **分頁**: 使用 cursor-based pagination
2. **快取**: Redis 快取熱門查詢
3. **預載**: Eager loading 避免 N+1 問題
4. **分區**: 考慮將 review_logs 按月份分區
### 容量規劃
- **預期用戶數**: 10,000
- **平均詞卡/用戶**: 1,000
- **平均評論/詞卡**: 50
- **總資料量**: ~10GB
## 🔒 安全考慮
1. **Row Level Security (RLS)**: Supabase 提供
2. **加密**: 敏感資料加密存儲
3. **稽核**: 所有資料變更記錄
4. **資料遮罩**: 個人資訊不外洩