13 KiB
13 KiB
DramaLing 資料庫 Schema 詳細設計
🗄️ 資料庫架構
技術棧
- Database: PostgreSQL 15+ (via Supabase)
- ORM: Prisma
- Migration Tool: Prisma Migrate
- Backup: Daily automated backups via Supabase
📦 資料表設計
1. 用戶相關表
users
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
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
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
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
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
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
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
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
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
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
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
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
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
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
);
🔗 關聯關係
主要關係
- users → flashcard_sets: 一對多 (用戶擁有多個卡組)
- flashcard_sets → flashcards: 一對多 (卡組包含多個詞卡)
- users → learning_records: 一對多 (用戶的學習記錄)
- flashcards → learning_records: 一對多 (每個詞卡的學習記錄)
- learning_records → review_logs: 一對多 (學習記錄的詳細日誌)
🔑 索引策略
主要索引
- 用戶查詢: email, username
- 詞卡查詢: word, set_id, user_id
- 學習查詢: next_review_at, user_id
- 統計查詢: user_id + date
- 全文搜索: 使用 PostgreSQL 的 GIN 索引於 tags 和 JSONB 欄位
🔄 資料遷移策略
版本控制
# Prisma migration commands
npx prisma migrate dev --name init
npx prisma migrate deploy
npx prisma migrate status
備份策略
- 自動備份: 每日 02:00 UTC
- 保留期限: 30 天
- 地理位置: 跨區域複製
- 恢復測試: 每季度一次
📊 效能優化
查詢優化
- 分頁: 使用 cursor-based pagination
- 快取: Redis 快取熱門查詢
- 預載: Eager loading 避免 N+1 問題
- 分區: 考慮將 review_logs 按月份分區
容量規劃
- 預期用戶數: 10,000
- 平均詞卡/用戶: 1,000
- 平均評論/詞卡: 50
- 總資料量: ~10GB
🔒 安全考慮
- Row Level Security (RLS): Supabase 提供
- 加密: 敏感資料加密存儲
- 稽核: 所有資料變更記錄
- 資料遮罩: 個人資訊不外洩