using Microsoft.EntityFrameworkCore; using DramaLing.Api.Models.Entities; using System.Text.Json; namespace DramaLing.Api.Data; public class DramaLingDbContext : DbContext { public DramaLingDbContext(DbContextOptions options) : base(options) { } // DbSets public DbSet Users { get; set; } public DbSet UserSettings { get; set; } public DbSet CardSets { get; set; } public DbSet Flashcards { get; set; } public DbSet Tags { get; set; } public DbSet FlashcardTags { get; set; } public DbSet StudySessions { get; set; } public DbSet StudyRecords { get; set; } public DbSet ErrorReports { get; set; } public DbSet DailyStats { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // 設定表名稱 (與 Supabase 一致) modelBuilder.Entity().ToTable("user_profiles"); modelBuilder.Entity().ToTable("user_settings"); modelBuilder.Entity().ToTable("card_sets"); modelBuilder.Entity().ToTable("flashcards"); modelBuilder.Entity().ToTable("tags"); modelBuilder.Entity().ToTable("flashcard_tags"); modelBuilder.Entity().ToTable("study_sessions"); modelBuilder.Entity().ToTable("study_records"); modelBuilder.Entity().ToTable("error_reports"); modelBuilder.Entity().ToTable("daily_stats"); // 配置屬性名稱 (snake_case) ConfigureUserEntity(modelBuilder); ConfigureFlashcardEntity(modelBuilder); ConfigureStudyEntities(modelBuilder); ConfigureTagEntities(modelBuilder); ConfigureErrorReportEntity(modelBuilder); ConfigureDailyStatsEntity(modelBuilder); // 複合主鍵 modelBuilder.Entity() .HasKey(ft => new { ft.FlashcardId, ft.TagId }); modelBuilder.Entity() .HasIndex(ds => new { ds.UserId, ds.Date }) .IsUnique(); // 外鍵關係 ConfigureRelationships(modelBuilder); } private void ConfigureUserEntity(ModelBuilder modelBuilder) { var userEntity = modelBuilder.Entity(); 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>(v, (System.Text.Json.JsonSerializerOptions)null) ?? new Dictionary()); 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(); flashcardEntity.Property(f => f.UserId).HasColumnName("user_id"); flashcardEntity.Property(f => f.CardSetId).HasColumnName("card_set_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(); 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(); 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"); } private void ConfigureTagEntities(ModelBuilder modelBuilder) { var tagEntity = modelBuilder.Entity(); 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(); 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(); 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(); 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() .HasOne(cs => cs.User) .WithMany(u => u.CardSets) .HasForeignKey(cs => cs.UserId) .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity() .HasOne(f => f.User) .WithMany(u => u.Flashcards) .HasForeignKey(f => f.UserId) .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity() .HasOne(f => f.CardSet) .WithMany(cs => cs.Flashcards) .HasForeignKey(f => f.CardSetId) .OnDelete(DeleteBehavior.Cascade); // Study relationships modelBuilder.Entity() .HasOne(ss => ss.User) .WithMany(u => u.StudySessions) .HasForeignKey(ss => ss.UserId) .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity() .HasOne(sr => sr.Flashcard) .WithMany(f => f.StudyRecords) .HasForeignKey(sr => sr.FlashcardId) .OnDelete(DeleteBehavior.Cascade); // Tag relationships modelBuilder.Entity() .HasOne(ft => ft.Flashcard) .WithMany(f => f.FlashcardTags) .HasForeignKey(ft => ft.FlashcardId) .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity() .HasOne(ft => ft.Tag) .WithMany(t => t.FlashcardTags) .HasForeignKey(ft => ft.TagId) .OnDelete(DeleteBehavior.Cascade); // Error report relationships modelBuilder.Entity() .HasOne(er => er.User) .WithMany(u => u.ErrorReports) .HasForeignKey(er => er.UserId) .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity() .HasOne(er => er.Flashcard) .WithMany(f => f.ErrorReports) .HasForeignKey(er => er.FlashcardId) .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity() .HasOne(er => er.ResolvedByUser) .WithMany() .HasForeignKey(er => er.ResolvedBy) .OnDelete(DeleteBehavior.SetNull); // User settings relationship modelBuilder.Entity() .HasOne(us => us.User) .WithOne(u => u.Settings) .HasForeignKey(us => us.UserId) .OnDelete(DeleteBehavior.Cascade); // Daily stats relationship modelBuilder.Entity() .HasOne(ds => ds.User) .WithMany(u => u.DailyStats) .HasForeignKey(ds => ds.UserId) .OnDelete(DeleteBehavior.Cascade); } }