feat: add core persistence model
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using Tiku.Domain.Catalog;
|
||||
|
||||
namespace Tiku.Infrastructure.Persistence.Configurations;
|
||||
|
||||
internal sealed class RegionConfiguration : IEntityTypeConfiguration<Region>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Region> builder)
|
||||
{
|
||||
builder.ConfigureTenantEntity("regions");
|
||||
builder.ConfigureTimestamps();
|
||||
builder.Property(entity => entity.LegacyId).HasMaxLength(64);
|
||||
builder.Property(entity => entity.Name).HasMaxLength(200);
|
||||
builder.Property(entity => entity.Code).HasMaxLength(50);
|
||||
builder.Property(entity => entity.ShortName).HasMaxLength(100);
|
||||
builder.Property(entity => entity.FullName).HasMaxLength(300);
|
||||
builder.Property(entity => entity.Icon).HasMaxLength(2048);
|
||||
builder.Property(entity => entity.Pinyin).HasMaxLength(255);
|
||||
builder.Property(entity => entity.Config).IsJson("{}");
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique();
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class RegionModuleConfiguration : IEntityTypeConfiguration<RegionModule>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<RegionModule> builder)
|
||||
{
|
||||
builder.ConfigureTenantEntity("region_modules");
|
||||
builder.ConfigureTimestamps();
|
||||
builder.Property(entity => entity.LegacyId).HasMaxLength(64);
|
||||
builder.Property(entity => entity.Name).HasMaxLength(200);
|
||||
builder.Property(entity => entity.Type).HasMaxLength(50);
|
||||
builder.Property(entity => entity.Icon).HasMaxLength(2048);
|
||||
builder.Property(entity => entity.Color).HasMaxLength(50);
|
||||
builder.Property(entity => entity.TextColor).HasMaxLength(50);
|
||||
builder.Property(entity => entity.Route).HasMaxLength(500);
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique();
|
||||
|
||||
builder.HasOne<Region>()
|
||||
.WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.RegionId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ModuleNodeConfiguration : IEntityTypeConfiguration<ModuleNode>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<ModuleNode> builder)
|
||||
{
|
||||
builder.ConfigureTenantEntity("module_nodes");
|
||||
builder.ConfigureTimestamps();
|
||||
builder.Property(entity => entity.LegacyId).HasMaxLength(64);
|
||||
builder.Property(entity => entity.LegacyParentId).HasMaxLength(64);
|
||||
builder.Property(entity => entity.LegacyModuleId).HasMaxLength(64);
|
||||
builder.Property(entity => entity.Type).HasSnakeCaseEnum();
|
||||
builder.Property(entity => entity.Name).HasMaxLength(300);
|
||||
builder.Property(entity => entity.Path).HasMaxLength(1000);
|
||||
builder.Property(entity => entity.Metadata).IsJson("{}");
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique();
|
||||
|
||||
builder.HasOne<Region>()
|
||||
.WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.RegionId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne<RegionModule>()
|
||||
.WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.ModuleId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne<ModuleNode>()
|
||||
.WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.ParentId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class SchoolConfiguration : IEntityTypeConfiguration<School>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<School> builder)
|
||||
{
|
||||
builder.ConfigureTenantEntity("schools");
|
||||
builder.ConfigureTimestamps();
|
||||
builder.Property(entity => entity.LegacyId).HasMaxLength(64);
|
||||
builder.Property(entity => entity.Name).HasMaxLength(300);
|
||||
builder.Property(entity => entity.ProfessionalExamDate).HasMaxLength(255);
|
||||
builder.Property(entity => entity.Metadata).IsJson("{}");
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique();
|
||||
|
||||
builder.HasOne<Region>()
|
||||
.WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.RegionId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne<RegionModule>()
|
||||
.WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.ModuleId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class MajorConfiguration : IEntityTypeConfiguration<Major>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Major> builder)
|
||||
{
|
||||
builder.ConfigureTenantEntity("majors");
|
||||
builder.ConfigureTimestamps();
|
||||
builder.Property(entity => entity.LegacyId).HasMaxLength(64);
|
||||
builder.Property(entity => entity.Name).HasMaxLength(300);
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique();
|
||||
|
||||
builder.HasOne<Region>()
|
||||
.WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.RegionId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne<School>()
|
||||
.WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.SchoolId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class SubjectConfiguration : IEntityTypeConfiguration<Subject>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Subject> builder)
|
||||
{
|
||||
builder.ConfigureTenantEntity("subjects");
|
||||
builder.ConfigureTimestamps();
|
||||
builder.Property(entity => entity.LegacyId).HasMaxLength(64);
|
||||
builder.Property(entity => entity.Name).HasMaxLength(300);
|
||||
builder.Property(entity => entity.Type).HasNullableSnakeCaseEnum();
|
||||
builder.Property(entity => entity.MajorLegacyIds).IsJson("[]");
|
||||
builder.Property(entity => entity.Icon).HasMaxLength(2048);
|
||||
builder.Property(entity => entity.Stats).IsJson("{}");
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique();
|
||||
|
||||
builder.HasOne<Region>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.RegionId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne<RegionModule>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.ModuleId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne<School>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.SchoolId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne<Major>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.MajorId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne<ModuleNode>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.NodeId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal sealed class CategoryConfiguration : IEntityTypeConfiguration<Category>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Category> builder)
|
||||
{
|
||||
builder.ConfigureTenantEntity("categories");
|
||||
builder.ConfigureTimestamps();
|
||||
builder.Property(entity => entity.LegacyId).HasMaxLength(64);
|
||||
builder.Property(entity => entity.Name).HasMaxLength(300);
|
||||
builder.Property(entity => entity.CategoryType).HasNullableSnakeCaseEnum();
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique();
|
||||
|
||||
builder.HasOne<Subject>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.SubjectId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne<ModuleNode>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.NodeId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using Tiku.Domain.Common;
|
||||
using Tiku.Domain.Tenancy;
|
||||
|
||||
namespace Tiku.Infrastructure.Persistence.Configurations;
|
||||
|
||||
internal static class ConfigurationSupport
|
||||
{
|
||||
public static void ConfigureEntity<TEntity>(
|
||||
this EntityTypeBuilder<TEntity> builder,
|
||||
string tableName)
|
||||
where TEntity : Entity
|
||||
{
|
||||
builder.ToTable(tableName);
|
||||
builder.HasKey(entity => entity.Id);
|
||||
builder.Property(entity => entity.Id).HasDefaultValueSql("gen_random_uuid()");
|
||||
}
|
||||
|
||||
public static void ConfigureTenantEntity<TEntity>(
|
||||
this EntityTypeBuilder<TEntity> builder,
|
||||
string tableName)
|
||||
where TEntity : TenantEntity
|
||||
{
|
||||
builder.ConfigureEntity(tableName);
|
||||
builder.HasAlternateKey(entity => new { entity.TenantId, entity.Id });
|
||||
builder.HasOne<Tenant>()
|
||||
.WithMany()
|
||||
.HasForeignKey(entity => entity.TenantId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
|
||||
public static void ConfigureTimestamps<TEntity>(this EntityTypeBuilder<TEntity> builder)
|
||||
where TEntity : class, IHasTimestamps
|
||||
{
|
||||
builder.Property(entity => entity.CreatedAt).HasDefaultValueSql("now()");
|
||||
builder.Property(entity => entity.UpdatedAt).HasDefaultValueSql("now()");
|
||||
}
|
||||
|
||||
public static PropertyBuilder<JsonElement> IsJson(
|
||||
this PropertyBuilder<JsonElement> property,
|
||||
string defaultJson)
|
||||
{
|
||||
return property
|
||||
.HasColumnType("jsonb")
|
||||
.HasDefaultValueSql($"'{defaultJson}'::jsonb");
|
||||
}
|
||||
|
||||
public static PropertyBuilder<TEnum> HasSnakeCaseEnum<TEnum>(
|
||||
this PropertyBuilder<TEnum> property,
|
||||
int maxLength = 32)
|
||||
where TEnum : struct, Enum
|
||||
{
|
||||
return property
|
||||
.HasConversion(new SnakeCaseEnumConverter<TEnum>())
|
||||
.HasMaxLength(maxLength);
|
||||
}
|
||||
|
||||
public static PropertyBuilder<TEnum?> HasNullableSnakeCaseEnum<TEnum>(
|
||||
this PropertyBuilder<TEnum?> property,
|
||||
int maxLength = 32)
|
||||
where TEnum : struct, Enum
|
||||
{
|
||||
return property
|
||||
.HasConversion(
|
||||
value => value.HasValue
|
||||
? ModelBuilderExtensions.ToSnakeCase(value.Value.ToString())
|
||||
: null,
|
||||
value => string.IsNullOrWhiteSpace(value)
|
||||
? null
|
||||
: Enum.Parse<TEnum>(value.Replace("_", string.Empty), true))
|
||||
.HasMaxLength(maxLength);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using Tiku.Domain.Catalog;
|
||||
using Tiku.Domain.Identity;
|
||||
|
||||
namespace Tiku.Infrastructure.Persistence.Configurations;
|
||||
|
||||
internal sealed class UserConfiguration : IEntityTypeConfiguration<User>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<User> builder)
|
||||
{
|
||||
builder.ConfigureEntity("users");
|
||||
builder.ConfigureTimestamps();
|
||||
|
||||
builder.Property(entity => entity.LegacyId).HasMaxLength(64);
|
||||
builder.Property(entity => entity.Username).HasMaxLength(100);
|
||||
builder.Property(entity => entity.Email).HasColumnType("citext").HasMaxLength(320);
|
||||
builder.Property(entity => entity.Phone).HasMaxLength(32);
|
||||
builder.Property(entity => entity.Name).HasMaxLength(200);
|
||||
builder.Property(entity => entity.AvatarUrl).HasMaxLength(2048);
|
||||
builder.Property(entity => entity.PrimaryRole).HasMaxLength(50);
|
||||
builder.Property(entity => entity.LegacyPasswordHash).HasMaxLength(512);
|
||||
builder.Property(entity => entity.RawProfile).IsJson("{}");
|
||||
|
||||
builder.HasIndex(entity => entity.LegacyId).IsUnique();
|
||||
builder.HasIndex(entity => entity.Username).IsUnique();
|
||||
builder.HasIndex(entity => entity.Email).IsUnique();
|
||||
builder.HasIndex(entity => entity.Phone).IsUnique();
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class UserIdentityConfiguration : IEntityTypeConfiguration<UserIdentity>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<UserIdentity> builder)
|
||||
{
|
||||
builder.ConfigureEntity("user_identities");
|
||||
builder.ConfigureTimestamps();
|
||||
|
||||
builder.Property(entity => entity.Provider).HasMaxLength(50);
|
||||
builder.Property(entity => entity.ProviderSubject).HasMaxLength(255);
|
||||
builder.Property(entity => entity.UnionId).HasMaxLength(255);
|
||||
builder.Property(entity => entity.OpenId).HasMaxLength(255);
|
||||
builder.Property(entity => entity.Phone).HasMaxLength(32);
|
||||
builder.Property(entity => entity.Email).HasColumnType("citext").HasMaxLength(320);
|
||||
builder.Property(entity => entity.SecretPayload).IsJson("{}");
|
||||
|
||||
builder.HasIndex(entity => new { entity.Provider, entity.ProviderSubject }).IsUnique();
|
||||
builder.HasOne<User>()
|
||||
.WithMany()
|
||||
.HasForeignKey(entity => entity.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class StudentProfileConfiguration : IEntityTypeConfiguration<StudentProfile>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<StudentProfile> builder)
|
||||
{
|
||||
builder.ConfigureTenantEntity("student_profiles");
|
||||
builder.ConfigureTimestamps();
|
||||
|
||||
builder.Property(entity => entity.LegacyUserId).HasMaxLength(64);
|
||||
builder.Property(entity => entity.Stats).IsJson("{}");
|
||||
builder.Property(entity => entity.Progress).IsJson("{}");
|
||||
builder.Property(entity => entity.ModuleSelections).IsJson("{}");
|
||||
builder.Property(entity => entity.RecentActivities).IsJson("[]");
|
||||
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.UserId }).IsUnique();
|
||||
|
||||
builder.HasOne<User>()
|
||||
.WithMany()
|
||||
.HasForeignKey(entity => entity.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
builder.HasOne<Region>()
|
||||
.WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.RegionId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne<School>()
|
||||
.WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.SelectedSchoolId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne<Major>()
|
||||
.WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.SelectedMajorId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using Tiku.Domain.Identity;
|
||||
using Tiku.Domain.Learning;
|
||||
using Tiku.Domain.QuestionBanks;
|
||||
using Tiku.Domain.Tenancy;
|
||||
|
||||
namespace Tiku.Infrastructure.Persistence.Configurations;
|
||||
|
||||
internal sealed class PracticeSessionConfiguration : IEntityTypeConfiguration<PracticeSession>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<PracticeSession> builder)
|
||||
{
|
||||
builder.ConfigureTenantEntity("practice_sessions");
|
||||
builder.HasAlternateKey(entity => new { entity.TenantId, entity.UserId, entity.Id });
|
||||
builder.Property(entity => entity.Mode).HasMaxLength(50);
|
||||
builder.Property(entity => entity.TargetType).HasMaxLength(50);
|
||||
builder.Property(entity => entity.StartedAt).HasDefaultValueSql("now()");
|
||||
builder.Property(entity => entity.Metadata).IsJson("{}");
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.UserId, entity.StartedAt });
|
||||
|
||||
builder.HasOne<User>().WithMany()
|
||||
.HasForeignKey(entity => entity.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class AnswerRecordConfiguration : IEntityTypeConfiguration<AnswerRecord>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<AnswerRecord> builder)
|
||||
{
|
||||
builder.ConfigureTenantEntity("answer_records");
|
||||
builder.Property(entity => entity.LegacyId).HasMaxLength(64);
|
||||
builder.Property(entity => entity.LegacyQuestionId).HasMaxLength(64);
|
||||
builder.Property(entity => entity.LegacyCategoryId).HasMaxLength(64);
|
||||
builder.Property(entity => entity.SelectedOptions).IsJson("[]");
|
||||
builder.Property(entity => entity.AnsweredAt).HasDefaultValueSql("now()");
|
||||
builder.Property(entity => entity.CreatedAt).HasDefaultValueSql("now()");
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique();
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.QuestionId });
|
||||
builder.HasIndex(entity => new
|
||||
{
|
||||
entity.TenantId,
|
||||
entity.QuestionId,
|
||||
entity.QuestionVersionId
|
||||
});
|
||||
builder.HasIndex(entity => new
|
||||
{
|
||||
entity.TenantId,
|
||||
entity.UserId,
|
||||
entity.PracticeSessionId
|
||||
});
|
||||
|
||||
builder.ToTable(table => table.HasCheckConstraint(
|
||||
"ck_answer_records_version_requires_question",
|
||||
"question_version_id is null or question_id is not null"));
|
||||
|
||||
builder.HasOne<User>().WithMany()
|
||||
.HasForeignKey(entity => entity.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
builder.HasOne<Question>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.QuestionId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne<QuestionVersion>().WithMany()
|
||||
.HasForeignKey(entity => new
|
||||
{
|
||||
entity.TenantId,
|
||||
entity.QuestionId,
|
||||
entity.QuestionVersionId
|
||||
})
|
||||
.HasPrincipalKey(entity => new
|
||||
{
|
||||
entity.TenantId,
|
||||
entity.QuestionId,
|
||||
entity.Id
|
||||
})
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne<PracticeSession>().WithMany()
|
||||
.HasForeignKey(entity => new
|
||||
{
|
||||
entity.TenantId,
|
||||
entity.UserId,
|
||||
entity.PracticeSessionId
|
||||
})
|
||||
.HasPrincipalKey(entity => new
|
||||
{
|
||||
entity.TenantId,
|
||||
entity.UserId,
|
||||
entity.Id
|
||||
})
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class FavoriteQuestionConfiguration : IEntityTypeConfiguration<FavoriteQuestion>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<FavoriteQuestion> builder)
|
||||
{
|
||||
builder.ToTable("favorite_questions");
|
||||
builder.HasKey(entity => new { entity.TenantId, entity.UserId, entity.QuestionId });
|
||||
builder.Property(entity => entity.Source).HasMaxLength(50);
|
||||
builder.Property(entity => entity.CreatedAt).HasDefaultValueSql("now()");
|
||||
|
||||
builder.HasOne<Tenant>().WithMany()
|
||||
.HasForeignKey(entity => entity.TenantId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
builder.HasOne<User>().WithMany()
|
||||
.HasForeignKey(entity => entity.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
builder.HasOne<Question>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.QuestionId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class WrongQuestionConfiguration : IEntityTypeConfiguration<WrongQuestion>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<WrongQuestion> builder)
|
||||
{
|
||||
builder.ToTable("wrong_questions");
|
||||
builder.HasKey(entity => new { entity.TenantId, entity.UserId, entity.QuestionId });
|
||||
builder.Property(entity => entity.WrongCount).HasDefaultValue(1);
|
||||
builder.Property(entity => entity.LastWrongAt).HasDefaultValueSql("now()");
|
||||
|
||||
builder.HasOne<Tenant>().WithMany()
|
||||
.HasForeignKey(entity => entity.TenantId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
builder.HasOne<User>().WithMany()
|
||||
.HasForeignKey(entity => entity.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
builder.HasOne<Question>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.QuestionId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class RecentPracticeConfiguration : IEntityTypeConfiguration<RecentPractice>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<RecentPractice> builder)
|
||||
{
|
||||
builder.ConfigureTenantEntity("recent_practices");
|
||||
builder.ConfigureTimestamps();
|
||||
builder.Property(entity => entity.LegacyId).HasMaxLength(64);
|
||||
builder.Property(entity => entity.PracticeType).HasMaxLength(50);
|
||||
builder.Property(entity => entity.TargetLegacyId).HasMaxLength(64);
|
||||
builder.Property(entity => entity.TargetName).HasMaxLength(300);
|
||||
builder.Property(entity => entity.Color).HasMaxLength(50);
|
||||
builder.Property(entity => entity.Metadata).IsJson("{}");
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique();
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.UserId, entity.LastAccessAt });
|
||||
|
||||
builder.HasOne<User>().WithMany()
|
||||
.HasForeignKey(entity => entity.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using Tiku.Domain.Catalog;
|
||||
using Tiku.Domain.Identity;
|
||||
using Tiku.Domain.QuestionBanks;
|
||||
|
||||
namespace Tiku.Infrastructure.Persistence.Configurations;
|
||||
|
||||
internal sealed class QuestionBankConfiguration : IEntityTypeConfiguration<QuestionBank>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<QuestionBank> builder)
|
||||
{
|
||||
builder.ConfigureTenantEntity("question_banks");
|
||||
builder.ConfigureTimestamps();
|
||||
builder.Property(entity => entity.Name).HasMaxLength(300);
|
||||
builder.Property(entity => entity.SourceScope).HasSnakeCaseEnum();
|
||||
builder.Property(entity => entity.Status).HasSnakeCaseEnum();
|
||||
builder.Property(entity => entity.Metadata).IsJson("{}");
|
||||
|
||||
builder.HasOne<Region>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.RegionId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class QuestionConfiguration : IEntityTypeConfiguration<Question>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Question> builder)
|
||||
{
|
||||
builder.ConfigureTenantEntity("questions");
|
||||
builder.ConfigureTimestamps();
|
||||
builder.Property(entity => entity.LegacyId).HasMaxLength(64);
|
||||
builder.Property(entity => entity.LegacySubjectId).HasMaxLength(64);
|
||||
builder.Property(entity => entity.LegacyCategoryId).HasMaxLength(64);
|
||||
builder.Property(entity => entity.LegacyNodeId).HasMaxLength(64);
|
||||
builder.Property(entity => entity.Type).HasMaxLength(50);
|
||||
builder.Property(entity => entity.TypeLabel).HasMaxLength(100);
|
||||
builder.Property(entity => entity.Tags).IsJson("[]");
|
||||
builder.Property(entity => entity.MediaUrl).HasMaxLength(2048);
|
||||
builder.Property(entity => entity.Status).HasSnakeCaseEnum();
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique();
|
||||
|
||||
builder.HasOne<QuestionBank>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.QuestionBankId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne<Subject>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.SubjectId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne<Category>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.CategoryId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne<ModuleNode>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.NodeId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
builder.HasOne<QuestionVersion>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.Id, entity.CurrentVersionId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.QuestionId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class QuestionVersionConfiguration : IEntityTypeConfiguration<QuestionVersion>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<QuestionVersion> builder)
|
||||
{
|
||||
builder.ConfigureEntity("question_versions");
|
||||
builder.HasAlternateKey(entity => new { entity.TenantId, entity.Id });
|
||||
builder.HasAlternateKey(entity => new { entity.TenantId, entity.QuestionId, entity.Id });
|
||||
|
||||
builder.Property(entity => entity.Options).IsJson("[]");
|
||||
builder.Property(entity => entity.CorrectOptionIndices).IsJson("[]");
|
||||
builder.Property(entity => entity.SubQuestions).IsJson("[]");
|
||||
builder.Property(entity => entity.CodeLang).HasMaxLength(50);
|
||||
builder.Property(entity => entity.SourceHash).HasMaxLength(128);
|
||||
builder.Property(entity => entity.CreatedAt).HasDefaultValueSql("now()");
|
||||
|
||||
builder.HasIndex(entity => new { entity.QuestionId, entity.VersionNo }).IsUnique();
|
||||
builder.HasOne<Question>().WithMany()
|
||||
.HasForeignKey(entity => new { entity.TenantId, entity.QuestionId })
|
||||
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
builder.HasOne<User>().WithMany()
|
||||
.HasForeignKey(entity => entity.CreatedBy)
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using Tiku.Domain.Identity;
|
||||
using Tiku.Domain.Tenancy;
|
||||
|
||||
namespace Tiku.Infrastructure.Persistence.Configurations;
|
||||
|
||||
internal sealed class TenantConfiguration : IEntityTypeConfiguration<Tenant>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Tenant> builder)
|
||||
{
|
||||
builder.ConfigureEntity("tenants");
|
||||
builder.ConfigureTimestamps();
|
||||
|
||||
builder.Property(entity => entity.Slug).HasColumnType("citext").HasMaxLength(100);
|
||||
builder.Property(entity => entity.Name).HasMaxLength(200);
|
||||
builder.Property(entity => entity.LegalName).HasMaxLength(300);
|
||||
builder.Property(entity => entity.Status).HasSnakeCaseEnum();
|
||||
builder.Property(entity => entity.Mode).HasSnakeCaseEnum();
|
||||
builder.Property(entity => entity.BillingStatus).HasSnakeCaseEnum();
|
||||
builder.Property(entity => entity.LegacyId).HasMaxLength(64);
|
||||
builder.Property(entity => entity.Metadata).IsJson("{}");
|
||||
|
||||
builder.HasIndex(entity => entity.Slug).IsUnique();
|
||||
builder.HasIndex(entity => entity.LegacyId).IsUnique();
|
||||
|
||||
builder.HasOne<User>()
|
||||
.WithMany()
|
||||
.HasForeignKey(entity => entity.OwnerUserId)
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class TenantMembershipConfiguration : IEntityTypeConfiguration<TenantMembership>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<TenantMembership> builder)
|
||||
{
|
||||
builder.ConfigureTenantEntity("tenant_memberships");
|
||||
builder.ConfigureTimestamps();
|
||||
|
||||
builder.Property(entity => entity.Role).HasSnakeCaseEnum();
|
||||
builder.Property(entity => entity.Status).HasSnakeCaseEnum();
|
||||
builder.Property(entity => entity.Permissions).IsJson("{}");
|
||||
builder.Property(entity => entity.LegacyRole).HasMaxLength(50);
|
||||
|
||||
builder.HasIndex(entity => new { entity.TenantId, entity.UserId, entity.Role }).IsUnique();
|
||||
|
||||
builder.HasOne<User>()
|
||||
.WithMany()
|
||||
.HasForeignKey(entity => entity.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user