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

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
);

🔗 關聯關係

主要關係

  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 欄位

🔄 資料遷移策略

版本控制

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