480 lines
26 KiB
C#
480 lines
26 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; }
|
|
public DbSet<StudySession> StudySessions { get; set; }
|
|
public DbSet<StudyRecord> StudyRecords { get; set; }
|
|
public DbSet<StudyCard> StudyCards { get; set; }
|
|
public DbSet<TestResult> TestResults { get; set; }
|
|
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; }
|
|
|
|
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");
|
|
modelBuilder.Entity<StudySession>().ToTable("study_sessions");
|
|
modelBuilder.Entity<StudyRecord>().ToTable("study_records");
|
|
modelBuilder.Entity<StudyCard>().ToTable("study_cards");
|
|
modelBuilder.Entity<TestResult>().ToTable("test_results");
|
|
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");
|
|
|
|
// 配置屬性名稱 (snake_case)
|
|
ConfigureUserEntity(modelBuilder);
|
|
ConfigureFlashcardEntity(modelBuilder);
|
|
ConfigureStudyEntities(modelBuilder);
|
|
ConfigureTagEntities(modelBuilder);
|
|
ConfigureErrorReportEntity(modelBuilder);
|
|
ConfigureDailyStatsEntity(modelBuilder);
|
|
ConfigureAudioEntities(modelBuilder);
|
|
ConfigureImageGenerationEntities(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.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.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.UserId).HasColumnName("user_id");
|
|
flashcardEntity.Property(f => f.PartOfSpeech).HasColumnName("part_of_speech");
|
|
flashcardEntity.Property(f => f.ExampleTranslation).HasColumnName("example_translation");
|
|
flashcardEntity.Property(f => f.EasinessFactor).HasColumnName("easiness_factor");
|
|
flashcardEntity.Property(f => f.IntervalDays).HasColumnName("interval_days");
|
|
flashcardEntity.Property(f => f.NextReviewDate).HasColumnName("next_review_date");
|
|
flashcardEntity.Property(f => f.MasteryLevel).HasColumnName("mastery_level");
|
|
flashcardEntity.Property(f => f.TimesReviewed).HasColumnName("times_reviewed");
|
|
flashcardEntity.Property(f => f.TimesCorrect).HasColumnName("times_correct");
|
|
flashcardEntity.Property(f => f.LastReviewedAt).HasColumnName("last_reviewed_at");
|
|
flashcardEntity.Property(f => f.IsFavorite).HasColumnName("is_favorite");
|
|
flashcardEntity.Property(f => f.IsArchived).HasColumnName("is_archived");
|
|
flashcardEntity.Property(f => f.DifficultyLevel).HasColumnName("difficulty_level");
|
|
flashcardEntity.Property(f => f.CreatedAt).HasColumnName("created_at");
|
|
flashcardEntity.Property(f => f.UpdatedAt).HasColumnName("updated_at");
|
|
}
|
|
|
|
private void ConfigureStudyEntities(ModelBuilder modelBuilder)
|
|
{
|
|
var sessionEntity = modelBuilder.Entity<StudySession>();
|
|
sessionEntity.Property(s => s.UserId).HasColumnName("user_id");
|
|
sessionEntity.Property(s => s.SessionType).HasColumnName("session_type");
|
|
sessionEntity.Property(s => s.StartedAt).HasColumnName("started_at");
|
|
sessionEntity.Property(s => s.EndedAt).HasColumnName("ended_at");
|
|
sessionEntity.Property(s => s.TotalCards).HasColumnName("total_cards");
|
|
sessionEntity.Property(s => s.CorrectCount).HasColumnName("correct_count");
|
|
sessionEntity.Property(s => s.DurationSeconds).HasColumnName("duration_seconds");
|
|
sessionEntity.Property(s => s.AverageResponseTimeMs).HasColumnName("average_response_time_ms");
|
|
|
|
var recordEntity = modelBuilder.Entity<StudyRecord>();
|
|
recordEntity.Property(r => r.UserId).HasColumnName("user_id");
|
|
recordEntity.Property(r => r.FlashcardId).HasColumnName("flashcard_id");
|
|
recordEntity.Property(r => r.SessionId).HasColumnName("session_id");
|
|
recordEntity.Property(r => r.StudyMode).HasColumnName("study_mode");
|
|
recordEntity.Property(r => r.QualityRating).HasColumnName("quality_rating");
|
|
recordEntity.Property(r => r.ResponseTimeMs).HasColumnName("response_time_ms");
|
|
recordEntity.Property(r => r.UserAnswer).HasColumnName("user_answer");
|
|
recordEntity.Property(r => r.IsCorrect).HasColumnName("is_correct");
|
|
recordEntity.Property(r => r.StudiedAt).HasColumnName("studied_at");
|
|
|
|
// 添加複合唯一索引:防止同一用戶同一詞卡同一測驗類型重複記錄
|
|
recordEntity.HasIndex(r => new { r.UserId, r.FlashcardId, r.StudyMode })
|
|
.IsUnique()
|
|
.HasDatabaseName("IX_StudyRecord_UserCard_TestType_Unique");
|
|
}
|
|
|
|
private void ConfigureTagEntities(ModelBuilder modelBuilder)
|
|
{
|
|
var tagEntity = modelBuilder.Entity<Tag>();
|
|
tagEntity.Property(t => t.UserId).HasColumnName("user_id");
|
|
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.UserId).HasColumnName("user_id");
|
|
errorEntity.Property(e => e.FlashcardId).HasColumnName("flashcard_id");
|
|
errorEntity.Property(e => e.ReportType).HasColumnName("report_type");
|
|
errorEntity.Property(e => e.StudyMode).HasColumnName("study_mode");
|
|
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.UserId).HasColumnName("user_id");
|
|
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 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
|
|
modelBuilder.Entity<StudySession>()
|
|
.HasOne(ss => ss.User)
|
|
.WithMany(u => u.StudySessions)
|
|
.HasForeignKey(ss => ss.UserId)
|
|
.OnDelete(DeleteBehavior.Cascade);
|
|
|
|
modelBuilder.Entity<StudyRecord>()
|
|
.HasOne(sr => sr.Flashcard)
|
|
.WithMany(f => f.StudyRecords)
|
|
.HasForeignKey(sr => sr.FlashcardId)
|
|
.OnDelete(DeleteBehavior.Cascade);
|
|
|
|
// 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.TextHash).HasColumnName("text_hash");
|
|
audioCacheEntity.Property(ac => ac.TextContent).HasColumnName("text_content");
|
|
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.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");
|
|
pronunciationEntity.Property(pa => pa.StudySessionId).HasColumnName("study_session_id");
|
|
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");
|
|
|
|
pronunciationEntity.HasIndex(pa => pa.StudySessionId)
|
|
.HasDatabaseName("IX_PronunciationAssessment_Session");
|
|
|
|
// UserAudioPreferences configuration
|
|
var audioPrefsEntity = modelBuilder.Entity<UserAudioPreferences>();
|
|
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);
|
|
|
|
modelBuilder.Entity<PronunciationAssessment>()
|
|
.HasOne(pa => pa.StudySession)
|
|
.WithMany()
|
|
.HasForeignKey(pa => pa.StudySessionId)
|
|
.OnDelete(DeleteBehavior.SetNull);
|
|
|
|
// 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.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.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);
|
|
}
|
|
} |