549 lines
30 KiB
C#
549 lines
30 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using DramaLing.Api.Models.Entities;
|
|
using System.Text.Json;
|
|
|
|
namespace DramaLing.Api.Data;
|
|
|
|
public class DramaLingDbContext : DbContext
|
|
{
|
|
public DramaLingDbContext(DbContextOptions<DramaLingDbContext> options) : base(options)
|
|
{
|
|
}
|
|
|
|
// DbSets
|
|
public DbSet<User> Users { get; set; }
|
|
public DbSet<UserSettings> UserSettings { get; set; }
|
|
public DbSet<Flashcard> Flashcards { get; set; }
|
|
public DbSet<Tag> Tags { get; set; }
|
|
public DbSet<FlashcardTag> FlashcardTags { get; set; }
|
|
// StudyRecord removed - study system cleaned
|
|
public DbSet<ErrorReport> ErrorReports { get; set; }
|
|
public DbSet<DailyStats> DailyStats { get; set; }
|
|
public DbSet<SentenceAnalysisCache> SentenceAnalysisCache { get; set; }
|
|
public DbSet<WordQueryUsageStats> WordQueryUsageStats { get; set; }
|
|
public DbSet<AudioCache> AudioCaches { get; set; }
|
|
public DbSet<PronunciationAssessment> PronunciationAssessments { get; set; }
|
|
public DbSet<UserAudioPreferences> UserAudioPreferences { get; set; }
|
|
public DbSet<ExampleImage> ExampleImages { get; set; }
|
|
public DbSet<FlashcardExampleImage> FlashcardExampleImages { get; set; }
|
|
public DbSet<ImageGenerationRequest> ImageGenerationRequests { get; set; }
|
|
public DbSet<OptionsVocabulary> OptionsVocabularies { get; set; }
|
|
public DbSet<FlashcardReview> FlashcardReviews { get; set; }
|
|
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
base.OnModelCreating(modelBuilder);
|
|
|
|
// 設定表名稱 (與 Supabase 一致)
|
|
modelBuilder.Entity<User>().ToTable("user_profiles");
|
|
modelBuilder.Entity<UserSettings>().ToTable("user_settings");
|
|
modelBuilder.Entity<Flashcard>().ToTable("flashcards");
|
|
modelBuilder.Entity<Tag>().ToTable("tags");
|
|
modelBuilder.Entity<FlashcardTag>().ToTable("flashcard_tags");
|
|
// StudyRecord table removed
|
|
modelBuilder.Entity<ErrorReport>().ToTable("error_reports");
|
|
modelBuilder.Entity<DailyStats>().ToTable("daily_stats");
|
|
modelBuilder.Entity<AudioCache>().ToTable("audio_cache");
|
|
modelBuilder.Entity<PronunciationAssessment>().ToTable("pronunciation_assessments");
|
|
modelBuilder.Entity<UserAudioPreferences>().ToTable("user_audio_preferences");
|
|
modelBuilder.Entity<ExampleImage>().ToTable("example_images");
|
|
modelBuilder.Entity<FlashcardExampleImage>().ToTable("flashcard_example_images");
|
|
modelBuilder.Entity<ImageGenerationRequest>().ToTable("image_generation_requests");
|
|
modelBuilder.Entity<OptionsVocabulary>().ToTable("options_vocabularies");
|
|
modelBuilder.Entity<SentenceAnalysisCache>().ToTable("sentence_analysis_cache");
|
|
modelBuilder.Entity<WordQueryUsageStats>().ToTable("word_query_usage_stats");
|
|
|
|
// 配置屬性名稱 (snake_case)
|
|
ConfigureUserEntity(modelBuilder);
|
|
ConfigureUserSettingsEntity(modelBuilder);
|
|
ConfigureFlashcardEntity(modelBuilder);
|
|
// ConfigureStudyEntities 已移除 - StudyRecord 實體已清理
|
|
ConfigureTagEntities(modelBuilder);
|
|
ConfigureErrorReportEntity(modelBuilder);
|
|
ConfigureDailyStatsEntity(modelBuilder);
|
|
ConfigureSentenceAnalysisCacheEntity(modelBuilder);
|
|
ConfigureWordQueryUsageStatsEntity(modelBuilder);
|
|
ConfigureAudioEntities(modelBuilder);
|
|
ConfigureImageGenerationEntities(modelBuilder);
|
|
ConfigureOptionsVocabularyEntity(modelBuilder);
|
|
|
|
// 複合主鍵
|
|
modelBuilder.Entity<FlashcardTag>()
|
|
.HasKey(ft => new { ft.FlashcardId, ft.TagId });
|
|
|
|
modelBuilder.Entity<FlashcardExampleImage>()
|
|
.HasKey(fei => new { fei.FlashcardId, fei.ExampleImageId });
|
|
|
|
modelBuilder.Entity<DailyStats>()
|
|
.HasIndex(ds => new { ds.UserId, ds.Date })
|
|
.IsUnique();
|
|
|
|
// 外鍵關係
|
|
ConfigureRelationships(modelBuilder);
|
|
}
|
|
|
|
private void ConfigureUserEntity(ModelBuilder modelBuilder)
|
|
{
|
|
var userEntity = modelBuilder.Entity<User>();
|
|
userEntity.Property(u => u.Id).HasColumnName("id");
|
|
userEntity.Property(u => u.Username).HasColumnName("username");
|
|
userEntity.Property(u => u.Email).HasColumnName("email");
|
|
userEntity.Property(u => u.PasswordHash).HasColumnName("password_hash");
|
|
userEntity.Property(u => u.DisplayName).HasColumnName("display_name");
|
|
userEntity.Property(u => u.AvatarUrl).HasColumnName("avatar_url");
|
|
userEntity.Property(u => u.SubscriptionType).HasColumnName("subscription_type");
|
|
userEntity.Property(u => u.Preferences)
|
|
.HasColumnName("preferences")
|
|
.HasConversion(
|
|
v => System.Text.Json.JsonSerializer.Serialize(v, (System.Text.Json.JsonSerializerOptions)null),
|
|
v => System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, object>>(v, (System.Text.Json.JsonSerializerOptions)null) ?? new Dictionary<string, object>());
|
|
|
|
// 新增個人化欄位映射
|
|
userEntity.Property(u => u.EnglishLevel).HasColumnName("english_level");
|
|
userEntity.Property(u => u.EnglishLevelNumeric)
|
|
.HasColumnName("english_level_numeric")
|
|
.HasDefaultValue(2); // 預設 A2
|
|
userEntity.Property(u => u.LevelUpdatedAt).HasColumnName("level_updated_at");
|
|
userEntity.Property(u => u.IsLevelVerified).HasColumnName("is_level_verified");
|
|
userEntity.Property(u => u.LevelNotes).HasColumnName("level_notes");
|
|
|
|
userEntity.Property(u => u.CreatedAt).HasColumnName("created_at");
|
|
userEntity.Property(u => u.UpdatedAt).HasColumnName("updated_at");
|
|
|
|
// Add unique indexes
|
|
userEntity.HasIndex(u => u.Email).IsUnique();
|
|
userEntity.HasIndex(u => u.Username).IsUnique();
|
|
}
|
|
|
|
private void ConfigureFlashcardEntity(ModelBuilder modelBuilder)
|
|
{
|
|
var flashcardEntity = modelBuilder.Entity<Flashcard>();
|
|
flashcardEntity.Property(f => f.Id).HasColumnName("id");
|
|
flashcardEntity.Property(f => f.UserId).HasColumnName("user_id");
|
|
flashcardEntity.Property(f => f.Word).HasColumnName("word");
|
|
flashcardEntity.Property(f => f.Translation).HasColumnName("translation");
|
|
flashcardEntity.Property(f => f.Definition).HasColumnName("definition");
|
|
flashcardEntity.Property(f => f.PartOfSpeech).HasColumnName("part_of_speech");
|
|
flashcardEntity.Property(f => f.Pronunciation).HasColumnName("pronunciation");
|
|
flashcardEntity.Property(f => f.Example).HasColumnName("example");
|
|
flashcardEntity.Property(f => f.ExampleTranslation).HasColumnName("example_translation");
|
|
// 已刪除的復習相關屬性配置
|
|
// EasinessFactor, IntervalDays, NextReviewDate, MasteryLevel,
|
|
// TimesReviewed, TimesCorrect, LastReviewedAt 已移除
|
|
flashcardEntity.Property(f => f.IsFavorite).HasColumnName("is_favorite");
|
|
flashcardEntity.Property(f => f.IsArchived).HasColumnName("is_archived");
|
|
|
|
// 難度等級映射 - 使用數字格式
|
|
flashcardEntity.Property(f => f.DifficultyLevelNumeric).HasColumnName("difficulty_level_numeric");
|
|
|
|
flashcardEntity.Property(f => f.CreatedAt).HasColumnName("created_at");
|
|
flashcardEntity.Property(f => f.UpdatedAt).HasColumnName("updated_at");
|
|
}
|
|
|
|
// ConfigureStudyEntities 方法已移除 - StudyRecord 實體已清理
|
|
|
|
private void ConfigureTagEntities(ModelBuilder modelBuilder)
|
|
{
|
|
var tagEntity = modelBuilder.Entity<Tag>();
|
|
tagEntity.Property(t => t.Id).HasColumnName("id");
|
|
tagEntity.Property(t => t.UserId).HasColumnName("user_id");
|
|
tagEntity.Property(t => t.Name).HasColumnName("name");
|
|
tagEntity.Property(t => t.Color).HasColumnName("color");
|
|
tagEntity.Property(t => t.UsageCount).HasColumnName("usage_count");
|
|
tagEntity.Property(t => t.CreatedAt).HasColumnName("created_at");
|
|
|
|
var flashcardTagEntity = modelBuilder.Entity<FlashcardTag>();
|
|
flashcardTagEntity.Property(ft => ft.FlashcardId).HasColumnName("flashcard_id");
|
|
flashcardTagEntity.Property(ft => ft.TagId).HasColumnName("tag_id");
|
|
}
|
|
|
|
private void ConfigureErrorReportEntity(ModelBuilder modelBuilder)
|
|
{
|
|
var errorEntity = modelBuilder.Entity<ErrorReport>();
|
|
errorEntity.Property(e => e.Id).HasColumnName("id");
|
|
errorEntity.Property(e => e.UserId).HasColumnName("user_id");
|
|
errorEntity.Property(e => e.FlashcardId).HasColumnName("flashcard_id");
|
|
errorEntity.Property(e => e.ReportType).HasColumnName("report_type");
|
|
errorEntity.Property(e => e.Description).HasColumnName("description");
|
|
errorEntity.Property(e => e.StudyMode).HasColumnName("study_mode");
|
|
errorEntity.Property(e => e.Status).HasColumnName("status");
|
|
errorEntity.Property(e => e.AdminNotes).HasColumnName("admin_notes");
|
|
errorEntity.Property(e => e.ResolvedAt).HasColumnName("resolved_at");
|
|
errorEntity.Property(e => e.ResolvedBy).HasColumnName("resolved_by");
|
|
errorEntity.Property(e => e.CreatedAt).HasColumnName("created_at");
|
|
}
|
|
|
|
private void ConfigureDailyStatsEntity(ModelBuilder modelBuilder)
|
|
{
|
|
var statsEntity = modelBuilder.Entity<DailyStats>();
|
|
statsEntity.Property(d => d.Id).HasColumnName("id");
|
|
statsEntity.Property(d => d.UserId).HasColumnName("user_id");
|
|
statsEntity.Property(d => d.Date).HasColumnName("date");
|
|
statsEntity.Property(d => d.WordsStudied).HasColumnName("words_studied");
|
|
statsEntity.Property(d => d.WordsCorrect).HasColumnName("words_correct");
|
|
statsEntity.Property(d => d.StudyTimeSeconds).HasColumnName("study_time_seconds");
|
|
statsEntity.Property(d => d.SessionCount).HasColumnName("session_count");
|
|
statsEntity.Property(d => d.CardsGenerated).HasColumnName("cards_generated");
|
|
statsEntity.Property(d => d.AiApiCalls).HasColumnName("ai_api_calls");
|
|
statsEntity.Property(d => d.CreatedAt).HasColumnName("created_at");
|
|
}
|
|
|
|
private void ConfigureUserSettingsEntity(ModelBuilder modelBuilder)
|
|
{
|
|
var settingsEntity = modelBuilder.Entity<UserSettings>();
|
|
settingsEntity.Property(us => us.Id).HasColumnName("id");
|
|
settingsEntity.Property(us => us.UserId).HasColumnName("user_id");
|
|
settingsEntity.Property(us => us.DailyGoal).HasColumnName("daily_goal");
|
|
settingsEntity.Property(us => us.ReminderTime).HasColumnName("reminder_time");
|
|
settingsEntity.Property(us => us.ReminderEnabled).HasColumnName("reminder_enabled");
|
|
settingsEntity.Property(us => us.DifficultyPreference).HasColumnName("difficulty_preference");
|
|
settingsEntity.Property(us => us.AutoPlayAudio).HasColumnName("auto_play_audio");
|
|
settingsEntity.Property(us => us.ShowPronunciation).HasColumnName("show_pronunciation");
|
|
settingsEntity.Property(us => us.CreatedAt).HasColumnName("created_at");
|
|
settingsEntity.Property(us => us.UpdatedAt).HasColumnName("updated_at");
|
|
}
|
|
|
|
private void ConfigureSentenceAnalysisCacheEntity(ModelBuilder modelBuilder)
|
|
{
|
|
var cacheEntity = modelBuilder.Entity<SentenceAnalysisCache>();
|
|
cacheEntity.Property(sac => sac.Id).HasColumnName("id");
|
|
cacheEntity.Property(sac => sac.InputTextHash).HasColumnName("input_text_hash");
|
|
cacheEntity.Property(sac => sac.InputText).HasColumnName("input_text");
|
|
cacheEntity.Property(sac => sac.CorrectedText).HasColumnName("corrected_text");
|
|
cacheEntity.Property(sac => sac.HasGrammarErrors).HasColumnName("has_grammar_errors");
|
|
cacheEntity.Property(sac => sac.GrammarCorrections).HasColumnName("grammar_corrections");
|
|
cacheEntity.Property(sac => sac.AnalysisResult).HasColumnName("analysis_result");
|
|
cacheEntity.Property(sac => sac.HighValueWords).HasColumnName("high_value_words");
|
|
cacheEntity.Property(sac => sac.IdiomsDetected).HasColumnName("idioms_detected");
|
|
cacheEntity.Property(sac => sac.CreatedAt).HasColumnName("created_at");
|
|
cacheEntity.Property(sac => sac.ExpiresAt).HasColumnName("expires_at");
|
|
cacheEntity.Property(sac => sac.AccessCount).HasColumnName("access_count");
|
|
cacheEntity.Property(sac => sac.LastAccessedAt).HasColumnName("last_accessed_at");
|
|
}
|
|
|
|
private void ConfigureWordQueryUsageStatsEntity(ModelBuilder modelBuilder)
|
|
{
|
|
var statsEntity = modelBuilder.Entity<WordQueryUsageStats>();
|
|
statsEntity.Property(wq => wq.Id).HasColumnName("id");
|
|
statsEntity.Property(wq => wq.UserId).HasColumnName("user_id");
|
|
statsEntity.Property(wq => wq.Date).HasColumnName("date");
|
|
statsEntity.Property(wq => wq.SentenceAnalysisCount).HasColumnName("sentence_analysis_count");
|
|
statsEntity.Property(wq => wq.HighValueWordClicks).HasColumnName("high_value_word_clicks");
|
|
statsEntity.Property(wq => wq.LowValueWordClicks).HasColumnName("low_value_word_clicks");
|
|
statsEntity.Property(wq => wq.TotalApiCalls).HasColumnName("total_api_calls");
|
|
statsEntity.Property(wq => wq.UniqueWordsQueried).HasColumnName("unique_words_queried");
|
|
statsEntity.Property(wq => wq.CreatedAt).HasColumnName("created_at");
|
|
statsEntity.Property(wq => wq.UpdatedAt).HasColumnName("updated_at");
|
|
}
|
|
|
|
private void ConfigureRelationships(ModelBuilder modelBuilder)
|
|
{
|
|
// User relationships
|
|
modelBuilder.Entity<Flashcard>()
|
|
.HasOne(f => f.User)
|
|
.WithMany(u => u.Flashcards)
|
|
.HasForeignKey(f => f.UserId)
|
|
.OnDelete(DeleteBehavior.Cascade);
|
|
|
|
// Study relationships 已移除 - StudyRecord 實體已清理
|
|
|
|
// FlashcardReview relationships
|
|
modelBuilder.Entity<FlashcardReview>()
|
|
.HasOne(fr => fr.Flashcard)
|
|
.WithMany()
|
|
.HasForeignKey(fr => fr.FlashcardId)
|
|
.OnDelete(DeleteBehavior.Cascade);
|
|
|
|
modelBuilder.Entity<FlashcardReview>()
|
|
.HasOne(fr => fr.User)
|
|
.WithMany()
|
|
.HasForeignKey(fr => fr.UserId)
|
|
.OnDelete(DeleteBehavior.Cascade);
|
|
|
|
// 複習記錄唯一性約束 (每個用戶每張卡片只能有一條記錄)
|
|
modelBuilder.Entity<FlashcardReview>()
|
|
.HasIndex(fr => new { fr.FlashcardId, fr.UserId })
|
|
.IsUnique();
|
|
|
|
// Tag relationships
|
|
modelBuilder.Entity<FlashcardTag>()
|
|
.HasOne(ft => ft.Flashcard)
|
|
.WithMany(f => f.FlashcardTags)
|
|
.HasForeignKey(ft => ft.FlashcardId)
|
|
.OnDelete(DeleteBehavior.Cascade);
|
|
|
|
modelBuilder.Entity<FlashcardTag>()
|
|
.HasOne(ft => ft.Tag)
|
|
.WithMany(t => t.FlashcardTags)
|
|
.HasForeignKey(ft => ft.TagId)
|
|
.OnDelete(DeleteBehavior.Cascade);
|
|
|
|
// Error report relationships
|
|
modelBuilder.Entity<ErrorReport>()
|
|
.HasOne(er => er.User)
|
|
.WithMany(u => u.ErrorReports)
|
|
.HasForeignKey(er => er.UserId)
|
|
.OnDelete(DeleteBehavior.Cascade);
|
|
|
|
modelBuilder.Entity<ErrorReport>()
|
|
.HasOne(er => er.Flashcard)
|
|
.WithMany(f => f.ErrorReports)
|
|
.HasForeignKey(er => er.FlashcardId)
|
|
.OnDelete(DeleteBehavior.Cascade);
|
|
|
|
modelBuilder.Entity<ErrorReport>()
|
|
.HasOne(er => er.ResolvedByUser)
|
|
.WithMany()
|
|
.HasForeignKey(er => er.ResolvedBy)
|
|
.OnDelete(DeleteBehavior.SetNull);
|
|
|
|
// User settings relationship
|
|
modelBuilder.Entity<UserSettings>()
|
|
.HasOne(us => us.User)
|
|
.WithOne(u => u.Settings)
|
|
.HasForeignKey<UserSettings>(us => us.UserId)
|
|
.OnDelete(DeleteBehavior.Cascade);
|
|
|
|
// Daily stats relationship
|
|
modelBuilder.Entity<DailyStats>()
|
|
.HasOne(ds => ds.User)
|
|
.WithMany(u => u.DailyStats)
|
|
.HasForeignKey(ds => ds.UserId)
|
|
.OnDelete(DeleteBehavior.Cascade);
|
|
|
|
// Sentence analysis cache configuration
|
|
modelBuilder.Entity<SentenceAnalysisCache>()
|
|
.HasIndex(sac => sac.InputTextHash)
|
|
.HasDatabaseName("IX_SentenceAnalysisCache_Hash");
|
|
|
|
modelBuilder.Entity<SentenceAnalysisCache>()
|
|
.HasIndex(sac => sac.ExpiresAt)
|
|
.HasDatabaseName("IX_SentenceAnalysisCache_Expires");
|
|
|
|
modelBuilder.Entity<SentenceAnalysisCache>()
|
|
.HasIndex(sac => new { sac.InputTextHash, sac.ExpiresAt })
|
|
.HasDatabaseName("IX_SentenceAnalysisCache_Hash_Expires");
|
|
|
|
// Word query usage stats configuration
|
|
modelBuilder.Entity<WordQueryUsageStats>()
|
|
.HasOne(wq => wq.User)
|
|
.WithMany()
|
|
.HasForeignKey(wq => wq.UserId)
|
|
.OnDelete(DeleteBehavior.Cascade);
|
|
|
|
modelBuilder.Entity<WordQueryUsageStats>()
|
|
.HasIndex(wq => new { wq.UserId, wq.Date })
|
|
.IsUnique()
|
|
.HasDatabaseName("IX_WordQueryUsageStats_UserDate");
|
|
|
|
modelBuilder.Entity<WordQueryUsageStats>()
|
|
.HasIndex(wq => wq.CreatedAt)
|
|
.HasDatabaseName("IX_WordQueryUsageStats_CreatedAt");
|
|
|
|
// Audio entities relationships
|
|
ConfigureAudioRelationships(modelBuilder);
|
|
}
|
|
|
|
private void ConfigureAudioEntities(ModelBuilder modelBuilder)
|
|
{
|
|
// AudioCache configuration
|
|
var audioCacheEntity = modelBuilder.Entity<AudioCache>();
|
|
audioCacheEntity.Property(ac => ac.Id).HasColumnName("id");
|
|
audioCacheEntity.Property(ac => ac.TextHash).HasColumnName("text_hash");
|
|
audioCacheEntity.Property(ac => ac.TextContent).HasColumnName("text_content");
|
|
audioCacheEntity.Property(ac => ac.Accent).HasColumnName("accent");
|
|
audioCacheEntity.Property(ac => ac.VoiceId).HasColumnName("voice_id");
|
|
audioCacheEntity.Property(ac => ac.AudioUrl).HasColumnName("audio_url");
|
|
audioCacheEntity.Property(ac => ac.FileSize).HasColumnName("file_size");
|
|
audioCacheEntity.Property(ac => ac.DurationMs).HasColumnName("duration_ms");
|
|
audioCacheEntity.Property(ac => ac.CreatedAt).HasColumnName("created_at");
|
|
audioCacheEntity.Property(ac => ac.LastAccessed).HasColumnName("last_accessed");
|
|
audioCacheEntity.Property(ac => ac.AccessCount).HasColumnName("access_count");
|
|
|
|
audioCacheEntity.HasIndex(ac => ac.TextHash)
|
|
.IsUnique()
|
|
.HasDatabaseName("IX_AudioCache_TextHash");
|
|
|
|
audioCacheEntity.HasIndex(ac => ac.LastAccessed)
|
|
.HasDatabaseName("IX_AudioCache_LastAccessed");
|
|
|
|
// PronunciationAssessment configuration
|
|
var pronunciationEntity = modelBuilder.Entity<PronunciationAssessment>();
|
|
pronunciationEntity.Property(pa => pa.Id).HasColumnName("id");
|
|
pronunciationEntity.Property(pa => pa.UserId).HasColumnName("user_id");
|
|
pronunciationEntity.Property(pa => pa.FlashcardId).HasColumnName("flashcard_id");
|
|
pronunciationEntity.Property(pa => pa.TargetText).HasColumnName("target_text");
|
|
pronunciationEntity.Property(pa => pa.AudioUrl).HasColumnName("audio_url");
|
|
pronunciationEntity.Property(pa => pa.OverallScore).HasColumnName("overall_score");
|
|
pronunciationEntity.Property(pa => pa.AccuracyScore).HasColumnName("accuracy_score");
|
|
pronunciationEntity.Property(pa => pa.FluencyScore).HasColumnName("fluency_score");
|
|
pronunciationEntity.Property(pa => pa.CompletenessScore).HasColumnName("completeness_score");
|
|
pronunciationEntity.Property(pa => pa.ProsodyScore).HasColumnName("prosody_score");
|
|
pronunciationEntity.Property(pa => pa.PhonemeScores).HasColumnName("phoneme_scores");
|
|
pronunciationEntity.Property(pa => pa.Suggestions).HasColumnName("suggestions");
|
|
// StudySessionId removed
|
|
pronunciationEntity.Property(pa => pa.PracticeMode).HasColumnName("practice_mode");
|
|
pronunciationEntity.Property(pa => pa.CreatedAt).HasColumnName("created_at");
|
|
|
|
pronunciationEntity.HasIndex(pa => new { pa.UserId, pa.FlashcardId })
|
|
.HasDatabaseName("IX_PronunciationAssessment_UserFlashcard");
|
|
|
|
// StudySessionId index removed
|
|
|
|
// UserAudioPreferences configuration
|
|
var audioPrefsEntity = modelBuilder.Entity<UserAudioPreferences>();
|
|
audioPrefsEntity.Property(uap => uap.UserId).HasColumnName("user_id");
|
|
audioPrefsEntity.Property(uap => uap.PreferredAccent).HasColumnName("preferred_accent");
|
|
audioPrefsEntity.Property(uap => uap.PreferredVoiceMale).HasColumnName("preferred_voice_male");
|
|
audioPrefsEntity.Property(uap => uap.PreferredVoiceFemale).HasColumnName("preferred_voice_female");
|
|
audioPrefsEntity.Property(uap => uap.DefaultSpeed).HasColumnName("default_speed");
|
|
audioPrefsEntity.Property(uap => uap.AutoPlayEnabled).HasColumnName("auto_play_enabled");
|
|
audioPrefsEntity.Property(uap => uap.PronunciationDifficulty).HasColumnName("pronunciation_difficulty");
|
|
audioPrefsEntity.Property(uap => uap.TargetScoreThreshold).HasColumnName("target_score_threshold");
|
|
audioPrefsEntity.Property(uap => uap.EnableDetailedFeedback).HasColumnName("enable_detailed_feedback");
|
|
audioPrefsEntity.Property(uap => uap.UpdatedAt).HasColumnName("updated_at");
|
|
}
|
|
|
|
private void ConfigureAudioRelationships(ModelBuilder modelBuilder)
|
|
{
|
|
// PronunciationAssessment relationships
|
|
modelBuilder.Entity<PronunciationAssessment>()
|
|
.HasOne(pa => pa.User)
|
|
.WithMany()
|
|
.HasForeignKey(pa => pa.UserId)
|
|
.OnDelete(DeleteBehavior.Cascade);
|
|
|
|
modelBuilder.Entity<PronunciationAssessment>()
|
|
.HasOne(pa => pa.Flashcard)
|
|
.WithMany()
|
|
.HasForeignKey(pa => pa.FlashcardId)
|
|
.OnDelete(DeleteBehavior.SetNull);
|
|
|
|
// StudySession relationship removed
|
|
|
|
// UserAudioPreferences relationship
|
|
modelBuilder.Entity<UserAudioPreferences>()
|
|
.HasOne(uap => uap.User)
|
|
.WithOne()
|
|
.HasForeignKey<UserAudioPreferences>(uap => uap.UserId)
|
|
.OnDelete(DeleteBehavior.Cascade);
|
|
}
|
|
|
|
private void ConfigureImageGenerationEntities(ModelBuilder modelBuilder)
|
|
{
|
|
// ExampleImage configuration
|
|
var exampleImageEntity = modelBuilder.Entity<ExampleImage>();
|
|
exampleImageEntity.Property(ei => ei.Id).HasColumnName("id");
|
|
exampleImageEntity.Property(ei => ei.RelativePath).HasColumnName("relative_path");
|
|
exampleImageEntity.Property(ei => ei.AltText).HasColumnName("alt_text");
|
|
exampleImageEntity.Property(ei => ei.GeminiPrompt).HasColumnName("gemini_prompt");
|
|
exampleImageEntity.Property(ei => ei.GeminiDescription).HasColumnName("gemini_description");
|
|
exampleImageEntity.Property(ei => ei.ReplicatePrompt).HasColumnName("replicate_prompt");
|
|
exampleImageEntity.Property(ei => ei.ReplicateModel).HasColumnName("replicate_model");
|
|
exampleImageEntity.Property(ei => ei.ReplicateVersion).HasColumnName("replicate_version");
|
|
exampleImageEntity.Property(ei => ei.GeminiCost).HasColumnName("gemini_cost");
|
|
exampleImageEntity.Property(ei => ei.ReplicateCost).HasColumnName("replicate_cost");
|
|
exampleImageEntity.Property(ei => ei.TotalGenerationCost).HasColumnName("total_generation_cost");
|
|
exampleImageEntity.Property(ei => ei.FileSize).HasColumnName("file_size");
|
|
exampleImageEntity.Property(ei => ei.ImageWidth).HasColumnName("image_width");
|
|
exampleImageEntity.Property(ei => ei.ImageHeight).HasColumnName("image_height");
|
|
exampleImageEntity.Property(ei => ei.ContentHash).HasColumnName("content_hash");
|
|
exampleImageEntity.Property(ei => ei.QualityScore).HasColumnName("quality_score");
|
|
exampleImageEntity.Property(ei => ei.ModerationStatus).HasColumnName("moderation_status");
|
|
exampleImageEntity.Property(ei => ei.ModerationNotes).HasColumnName("moderation_notes");
|
|
exampleImageEntity.Property(ei => ei.AccessCount).HasColumnName("access_count");
|
|
exampleImageEntity.Property(ei => ei.CreatedAt).HasColumnName("created_at");
|
|
exampleImageEntity.Property(ei => ei.UpdatedAt).HasColumnName("updated_at");
|
|
|
|
exampleImageEntity.HasIndex(ei => ei.ContentHash).IsUnique();
|
|
exampleImageEntity.HasIndex(ei => ei.AccessCount);
|
|
|
|
// FlashcardExampleImage configuration
|
|
var flashcardImageEntity = modelBuilder.Entity<FlashcardExampleImage>();
|
|
flashcardImageEntity.Property(fei => fei.FlashcardId).HasColumnName("flashcard_id");
|
|
flashcardImageEntity.Property(fei => fei.ExampleImageId).HasColumnName("example_image_id");
|
|
flashcardImageEntity.Property(fei => fei.DisplayOrder).HasColumnName("display_order");
|
|
flashcardImageEntity.Property(fei => fei.IsPrimary).HasColumnName("is_primary");
|
|
flashcardImageEntity.Property(fei => fei.ContextRelevance).HasColumnName("context_relevance");
|
|
flashcardImageEntity.Property(fei => fei.CreatedAt).HasColumnName("created_at");
|
|
|
|
// ImageGenerationRequest configuration
|
|
var generationRequestEntity = modelBuilder.Entity<ImageGenerationRequest>();
|
|
generationRequestEntity.Property(igr => igr.Id).HasColumnName("id");
|
|
generationRequestEntity.Property(igr => igr.UserId).HasColumnName("user_id");
|
|
generationRequestEntity.Property(igr => igr.FlashcardId).HasColumnName("flashcard_id");
|
|
generationRequestEntity.Property(igr => igr.OverallStatus).HasColumnName("overall_status");
|
|
generationRequestEntity.Property(igr => igr.GeminiStatus).HasColumnName("gemini_status");
|
|
generationRequestEntity.Property(igr => igr.ReplicateStatus).HasColumnName("replicate_status");
|
|
generationRequestEntity.Property(igr => igr.OriginalRequest).HasColumnName("original_request");
|
|
generationRequestEntity.Property(igr => igr.GeminiPrompt).HasColumnName("gemini_prompt");
|
|
generationRequestEntity.Property(igr => igr.GeneratedDescription).HasColumnName("generated_description");
|
|
generationRequestEntity.Property(igr => igr.FinalReplicatePrompt).HasColumnName("final_replicate_prompt");
|
|
generationRequestEntity.Property(igr => igr.GeneratedImageId).HasColumnName("generated_image_id");
|
|
generationRequestEntity.Property(igr => igr.GeminiErrorMessage).HasColumnName("gemini_error_message");
|
|
generationRequestEntity.Property(igr => igr.ReplicateErrorMessage).HasColumnName("replicate_error_message");
|
|
generationRequestEntity.Property(igr => igr.GeminiProcessingTimeMs).HasColumnName("gemini_processing_time_ms");
|
|
generationRequestEntity.Property(igr => igr.ReplicateProcessingTimeMs).HasColumnName("replicate_processing_time_ms");
|
|
generationRequestEntity.Property(igr => igr.TotalProcessingTimeMs).HasColumnName("total_processing_time_ms");
|
|
generationRequestEntity.Property(igr => igr.GeminiCost).HasColumnName("gemini_cost");
|
|
generationRequestEntity.Property(igr => igr.ReplicateCost).HasColumnName("replicate_cost");
|
|
generationRequestEntity.Property(igr => igr.TotalCost).HasColumnName("total_cost");
|
|
generationRequestEntity.Property(igr => igr.CreatedAt).HasColumnName("created_at");
|
|
generationRequestEntity.Property(igr => igr.GeminiStartedAt).HasColumnName("gemini_started_at");
|
|
generationRequestEntity.Property(igr => igr.GeminiCompletedAt).HasColumnName("gemini_completed_at");
|
|
generationRequestEntity.Property(igr => igr.ReplicateStartedAt).HasColumnName("replicate_started_at");
|
|
generationRequestEntity.Property(igr => igr.ReplicateCompletedAt).HasColumnName("replicate_completed_at");
|
|
generationRequestEntity.Property(igr => igr.CompletedAt).HasColumnName("completed_at");
|
|
|
|
// 關聯關係
|
|
flashcardImageEntity
|
|
.HasOne(fei => fei.Flashcard)
|
|
.WithMany(f => f.FlashcardExampleImages) // 指定反向導航屬性
|
|
.HasForeignKey(fei => fei.FlashcardId)
|
|
.OnDelete(DeleteBehavior.Cascade);
|
|
|
|
flashcardImageEntity
|
|
.HasOne(fei => fei.ExampleImage)
|
|
.WithMany(ei => ei.FlashcardExampleImages)
|
|
.HasForeignKey(fei => fei.ExampleImageId)
|
|
.OnDelete(DeleteBehavior.Cascade);
|
|
|
|
generationRequestEntity
|
|
.HasOne(igr => igr.User)
|
|
.WithMany()
|
|
.HasForeignKey(igr => igr.UserId)
|
|
.OnDelete(DeleteBehavior.Cascade);
|
|
|
|
generationRequestEntity
|
|
.HasOne(igr => igr.Flashcard)
|
|
.WithMany()
|
|
.HasForeignKey(igr => igr.FlashcardId)
|
|
.OnDelete(DeleteBehavior.Cascade);
|
|
|
|
generationRequestEntity
|
|
.HasOne(igr => igr.GeneratedImage)
|
|
.WithMany()
|
|
.HasForeignKey(igr => igr.GeneratedImageId)
|
|
.OnDelete(DeleteBehavior.SetNull);
|
|
}
|
|
|
|
private void ConfigureOptionsVocabularyEntity(ModelBuilder modelBuilder)
|
|
{
|
|
var optionsVocabEntity = modelBuilder.Entity<OptionsVocabulary>();
|
|
|
|
// Configure column names (snake_case)
|
|
optionsVocabEntity.Property(ov => ov.Id).HasColumnName("id");
|
|
optionsVocabEntity.Property(ov => ov.Word).HasColumnName("word");
|
|
optionsVocabEntity.Property(ov => ov.CEFRLevel).HasColumnName("cefr_level");
|
|
optionsVocabEntity.Property(ov => ov.PartOfSpeech).HasColumnName("part_of_speech");
|
|
optionsVocabEntity.Property(ov => ov.WordLength).HasColumnName("word_length");
|
|
optionsVocabEntity.Property(ov => ov.IsActive).HasColumnName("is_active");
|
|
optionsVocabEntity.Property(ov => ov.CreatedAt).HasColumnName("created_at");
|
|
optionsVocabEntity.Property(ov => ov.UpdatedAt).HasColumnName("updated_at");
|
|
|
|
// Configure default values
|
|
optionsVocabEntity.Property(ov => ov.IsActive).HasDefaultValue(true);
|
|
optionsVocabEntity.Property(ov => ov.CreatedAt).HasDefaultValueSql("CURRENT_TIMESTAMP");
|
|
optionsVocabEntity.Property(ov => ov.UpdatedAt).HasDefaultValueSql("CURRENT_TIMESTAMP");
|
|
}
|
|
} |