465 lines
13 KiB
Markdown
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. **資料遮罩**: 個人資訊不外洩 |