# 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. **資料遮罩**: 個人資訊不外洩