304 lines
15 KiB
C#
304 lines
15 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
|
using Tiku.Domain.Content;
|
|
using Tiku.Domain.Identity;
|
|
using Tiku.Domain.Learning;
|
|
using Tiku.Domain.Tenancy;
|
|
|
|
namespace Tiku.Infrastructure.Persistence.Configurations;
|
|
|
|
internal sealed class TenantBusinessLicenseConfiguration : IEntityTypeConfiguration<TenantBusinessLicense>
|
|
{
|
|
public void Configure(EntityTypeBuilder<TenantBusinessLicense> builder)
|
|
{
|
|
builder.ConfigureTenantEntity("tenant_business_licenses");
|
|
builder.ConfigureTimestamps();
|
|
builder.Property(entity => entity.Status).HasSnakeCaseEnum();
|
|
builder.Property(entity => entity.Version).IsConcurrencyToken().HasDefaultValue(1L);
|
|
builder.HasIndex(entity => new { entity.TenantId, entity.BusinessLineId }).IsUnique();
|
|
builder.HasOne<BusinessLine>().WithMany().HasForeignKey(entity => entity.BusinessLineId)
|
|
.OnDelete(DeleteBehavior.Restrict);
|
|
builder.ToTable(table => table.HasCheckConstraint(
|
|
"ck_tenant_business_license_period", "ends_at is null or ends_at > starts_at"));
|
|
}
|
|
}
|
|
|
|
internal sealed class TenantLicensedTargetConfiguration : IEntityTypeConfiguration<TenantLicensedTarget>
|
|
{
|
|
public void Configure(EntityTypeBuilder<TenantLicensedTarget> builder)
|
|
{
|
|
builder.ConfigureTenantOwned("tenant_licensed_targets");
|
|
builder.HasIndex(entity => new
|
|
{
|
|
entity.TenantId,
|
|
entity.TenantBusinessLicenseId,
|
|
entity.ExamTargetProfileVersionId
|
|
}).IsUnique();
|
|
builder.HasOne<TenantBusinessLicense>().WithMany()
|
|
.HasForeignKey(entity => new { entity.TenantId, Id = entity.TenantBusinessLicenseId })
|
|
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
|
.OnDelete(DeleteBehavior.Cascade);
|
|
builder.HasOne<ExamTargetProfileVersion>().WithMany()
|
|
.HasForeignKey(entity => entity.ExamTargetProfileVersionId)
|
|
.OnDelete(DeleteBehavior.Restrict);
|
|
builder.ToTable(table => table.HasCheckConstraint(
|
|
"ck_tenant_licensed_target_period", "ends_at is null or ends_at > starts_at"));
|
|
}
|
|
}
|
|
|
|
internal sealed class ProductAccessManifestConfiguration : IEntityTypeConfiguration<ProductAccessManifest>
|
|
{
|
|
public void Configure(EntityTypeBuilder<ProductAccessManifest> builder)
|
|
{
|
|
builder.ConfigureTenantEntity("product_access_manifests");
|
|
builder.ConfigureTimestamps();
|
|
builder.HasIndex(entity => new { entity.TenantId, entity.LearningProductId }).IsUnique();
|
|
builder.HasOne<LearningProduct>().WithMany()
|
|
.HasForeignKey(entity => new { entity.TenantId, Id = entity.LearningProductId })
|
|
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
|
.OnDelete(DeleteBehavior.Cascade);
|
|
builder.HasOne<ProductAccessManifestVersion>().WithMany()
|
|
.HasForeignKey(entity => new
|
|
{
|
|
entity.TenantId,
|
|
ProductAccessManifestId = entity.Id,
|
|
Id = entity.CurrentVersionId
|
|
})
|
|
.HasPrincipalKey(entity => new { entity.TenantId, entity.ProductAccessManifestId, entity.Id })
|
|
.OnDelete(DeleteBehavior.Restrict);
|
|
}
|
|
}
|
|
|
|
internal sealed class ProductAccessManifestVersionConfiguration :
|
|
IEntityTypeConfiguration<ProductAccessManifestVersion>
|
|
{
|
|
public void Configure(EntityTypeBuilder<ProductAccessManifestVersion> builder)
|
|
{
|
|
builder.ConfigureTenantOwned("product_access_manifest_versions");
|
|
builder.HasAlternateKey(entity => new { entity.TenantId, entity.ProductAccessManifestId, entity.Id });
|
|
builder.Property(entity => entity.Status).HasSnakeCaseEnum();
|
|
builder.Property(entity => entity.CreatedAt).HasDefaultValueSql("now()");
|
|
builder.HasIndex(entity => new { entity.TenantId, entity.ProductAccessManifestId, entity.VersionNo }).IsUnique();
|
|
builder.HasOne<ProductAccessManifest>().WithMany()
|
|
.HasForeignKey(entity => new { entity.TenantId, Id = entity.ProductAccessManifestId })
|
|
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
|
.OnDelete(DeleteBehavior.Cascade);
|
|
builder.ToTable(table =>
|
|
{
|
|
table.HasCheckConstraint("ck_product_manifest_active_targets", "max_active_targets between 1 and 16");
|
|
table.HasCheckConstraint("ck_product_manifest_alternate_targets", "max_alternate_targets between 0 and max_active_targets");
|
|
table.HasCheckConstraint("ck_product_manifest_daily_question_limit",
|
|
"daily_question_limit is null or daily_question_limit >= 0");
|
|
table.HasCheckConstraint("ck_product_manifest_total_question_limit",
|
|
"total_question_limit is null or total_question_limit >= 0");
|
|
});
|
|
}
|
|
}
|
|
|
|
internal sealed class ProductManifestReleaseConfiguration : IEntityTypeConfiguration<ProductManifestRelease>
|
|
{
|
|
public void Configure(EntityTypeBuilder<ProductManifestRelease> builder)
|
|
{
|
|
builder.ConfigureTenantOwned("product_manifest_releases");
|
|
builder.HasIndex(entity => new
|
|
{
|
|
entity.TenantId,
|
|
entity.ProductAccessManifestVersionId,
|
|
entity.ContentOwnerTenantId,
|
|
entity.ContentReleaseId
|
|
}).IsUnique();
|
|
builder.HasOne<ProductAccessManifestVersion>().WithMany()
|
|
.HasForeignKey(entity => new { entity.TenantId, Id = entity.ProductAccessManifestVersionId })
|
|
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
|
.OnDelete(DeleteBehavior.Cascade);
|
|
builder.HasOne<ContentRelease>().WithMany()
|
|
.HasForeignKey(entity => new { TenantId = entity.ContentOwnerTenantId, Id = entity.ContentReleaseId })
|
|
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
|
.OnDelete(DeleteBehavior.Restrict);
|
|
builder.HasOne<PlatformContentPackageVersion>().WithMany()
|
|
.HasForeignKey(entity => new
|
|
{
|
|
TenantId = entity.PlatformContentPackageOwnerTenantId,
|
|
Id = entity.PlatformContentPackageVersionId
|
|
})
|
|
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
|
.OnDelete(DeleteBehavior.Restrict);
|
|
builder.ToTable(table => table.HasCheckConstraint(
|
|
"ck_product_manifest_release_platform_pair",
|
|
"(platform_content_package_owner_tenant_id is null) = (platform_content_package_version_id is null)"));
|
|
}
|
|
}
|
|
|
|
internal sealed class ProductManifestTargetConfiguration : IEntityTypeConfiguration<ProductManifestTarget>
|
|
{
|
|
public void Configure(EntityTypeBuilder<ProductManifestTarget> builder)
|
|
{
|
|
builder.ConfigureTenantOwned("product_manifest_targets");
|
|
builder.HasIndex(entity => new
|
|
{
|
|
entity.TenantId,
|
|
entity.ProductAccessManifestVersionId,
|
|
entity.ExamTargetProfileVersionId
|
|
}).IsUnique();
|
|
builder.HasOne<ProductAccessManifestVersion>().WithMany()
|
|
.HasForeignKey(entity => new { entity.TenantId, Id = entity.ProductAccessManifestVersionId })
|
|
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
|
.OnDelete(DeleteBehavior.Cascade);
|
|
builder.HasOne<ExamTargetProfileVersion>().WithMany()
|
|
.HasForeignKey(entity => entity.ExamTargetProfileVersionId)
|
|
.OnDelete(DeleteBehavior.Restrict);
|
|
builder.ToTable(table => table.HasCheckConstraint(
|
|
"ck_product_manifest_target_role", "may_be_primary or may_be_alternate"));
|
|
}
|
|
}
|
|
|
|
internal sealed class ProductManifestResourceConfiguration : IEntityTypeConfiguration<ProductManifestResource>
|
|
{
|
|
public void Configure(EntityTypeBuilder<ProductManifestResource> builder)
|
|
{
|
|
builder.ConfigureTenantOwned("product_manifest_resources");
|
|
builder.Property(entity => entity.ResourceType).HasSnakeCaseEnum();
|
|
builder.Property(entity => entity.DisplayName).HasMaxLength(300);
|
|
builder.HasIndex(entity => new
|
|
{
|
|
entity.TenantId,
|
|
entity.ProductAccessManifestVersionId,
|
|
entity.ResourceType,
|
|
entity.ResourceId
|
|
}).HasDatabaseName("ux_product_manifest_resource").IsUnique();
|
|
builder.HasIndex(entity => new
|
|
{
|
|
entity.TenantId,
|
|
entity.ProductAccessManifestVersionId,
|
|
entity.SortOrder,
|
|
entity.Id
|
|
}).HasDatabaseName("ix_product_manifest_resource_order");
|
|
builder.HasOne<ProductAccessManifestVersion>().WithMany()
|
|
.HasForeignKey(entity => new { entity.TenantId, Id = entity.ProductAccessManifestVersionId })
|
|
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
|
.OnDelete(DeleteBehavior.Cascade);
|
|
}
|
|
}
|
|
|
|
internal sealed class StudentEntitlementConfiguration : IEntityTypeConfiguration<StudentEntitlement>
|
|
{
|
|
public void Configure(EntityTypeBuilder<StudentEntitlement> builder)
|
|
{
|
|
builder.ConfigureTenantEntity("student_entitlements_v2");
|
|
builder.ConfigureTimestamps();
|
|
builder.Property(entity => entity.Status).HasSnakeCaseEnum();
|
|
builder.Property(entity => entity.SourceType).HasMaxLength(100);
|
|
builder.Property(entity => entity.RevokedReason).HasMaxLength(1000);
|
|
builder.ToTable(table => table.HasCheckConstraint(
|
|
"ck_student_entitlements_v2_used_question_count", "used_question_count >= 0"));
|
|
builder.HasIndex(entity => new
|
|
{
|
|
entity.TenantId,
|
|
entity.UserId,
|
|
entity.Status,
|
|
entity.StartsAt,
|
|
entity.EndsAt
|
|
});
|
|
builder.HasIndex(entity => new { entity.TenantId, entity.SourceType, entity.SourceId })
|
|
.HasFilter("source_id is not null");
|
|
builder.HasOne<User>().WithMany().HasForeignKey(entity => entity.UserId).OnDelete(DeleteBehavior.Cascade);
|
|
builder.HasOne<LearningProduct>().WithMany()
|
|
.HasForeignKey(entity => new { entity.TenantId, Id = entity.LearningProductId })
|
|
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
|
.OnDelete(DeleteBehavior.Restrict);
|
|
builder.HasOne<ProductAccessManifestVersion>().WithMany()
|
|
.HasForeignKey(entity => new { entity.TenantId, Id = entity.ProductAccessManifestVersionId })
|
|
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
|
.OnDelete(DeleteBehavior.Restrict);
|
|
builder.ToTable(table => table.HasCheckConstraint(
|
|
"ck_student_entitlement_v2_period", "ends_at is null or ends_at > starts_at"));
|
|
}
|
|
}
|
|
|
|
internal sealed class StudentTargetSelectionHistoryConfiguration :
|
|
IEntityTypeConfiguration<StudentTargetSelectionHistory>
|
|
{
|
|
public void Configure(EntityTypeBuilder<StudentTargetSelectionHistory> builder)
|
|
{
|
|
builder.ConfigureTenantEntity("student_target_selection_history");
|
|
builder.ConfigureTimestamps();
|
|
builder.Property(entity => entity.Role).HasSnakeCaseEnum();
|
|
builder.Property(entity => entity.SourceType).HasMaxLength(100);
|
|
builder.Property(entity => entity.Reason).HasMaxLength(1000);
|
|
builder.HasIndex(entity => new
|
|
{
|
|
entity.TenantId,
|
|
entity.UserId,
|
|
entity.BusinessLineId,
|
|
entity.ExamTargetProfileVersionId
|
|
}).IsUnique().HasFilter("is_current");
|
|
builder.HasIndex(entity => new { entity.TenantId, entity.UserId, entity.BusinessLineId })
|
|
.IsUnique()
|
|
.HasDatabaseName("ux_student_target_primary")
|
|
.HasFilter("is_current and role = 'primary'");
|
|
builder.HasIndex(entity => new
|
|
{
|
|
entity.TenantId,
|
|
entity.UserId,
|
|
entity.BusinessLineId,
|
|
entity.EffectiveAt
|
|
}).HasDatabaseName("ix_student_target_selection_history");
|
|
builder.HasIndex(entity => new { entity.TenantId, entity.SourceType, entity.SourceId });
|
|
builder.HasOne<User>().WithMany().HasForeignKey(entity => entity.UserId).OnDelete(DeleteBehavior.Cascade);
|
|
builder.HasOne<BusinessLine>().WithMany().HasForeignKey(entity => entity.BusinessLineId)
|
|
.OnDelete(DeleteBehavior.Restrict);
|
|
builder.HasOne<ExamTargetProfileVersion>().WithMany()
|
|
.HasForeignKey(entity => entity.ExamTargetProfileVersionId)
|
|
.OnDelete(DeleteBehavior.Restrict);
|
|
builder.HasOne<StudentEntitlement>().WithMany()
|
|
.HasForeignKey(entity => new { entity.TenantId, Id = entity.EntitlementId })
|
|
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
|
.OnDelete(DeleteBehavior.Restrict);
|
|
builder.ToTable(table => table.HasCheckConstraint(
|
|
"ck_student_target_selection_source",
|
|
"(source_type = 'student_entitlement' and entitlement_id = source_id) or " +
|
|
"(source_type = 'class_assignment_grant' and entitlement_id is null)"));
|
|
}
|
|
}
|
|
|
|
internal sealed class ClassAssignmentGrantConfiguration : IEntityTypeConfiguration<ClassAssignmentGrant>
|
|
{
|
|
public void Configure(EntityTypeBuilder<ClassAssignmentGrant> builder)
|
|
{
|
|
builder.ConfigureTenantEntity("class_assignment_grants");
|
|
builder.ConfigureTimestamps();
|
|
builder.Property(entity => entity.ResourceType).HasSnakeCaseEnum();
|
|
builder.Property(entity => entity.Status).HasSnakeCaseEnum();
|
|
builder.Property(entity => entity.RevokedReason).HasMaxLength(1000);
|
|
builder.HasIndex(entity => new { entity.TenantId, entity.ClassId, entity.Status, entity.StartsAt });
|
|
builder.HasIndex(entity => new { entity.TenantId, entity.ResourceType, entity.ResourceId, entity.Status });
|
|
builder.HasOne<TenantClass>().WithMany()
|
|
.HasForeignKey(entity => new { entity.TenantId, Id = entity.ClassId })
|
|
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
|
.OnDelete(DeleteBehavior.Cascade);
|
|
builder.HasOne<ProductAccessManifestVersion>().WithMany()
|
|
.HasForeignKey(entity => new { entity.TenantId, Id = entity.ProductAccessManifestVersionId })
|
|
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
|
|
.OnDelete(DeleteBehavior.Restrict);
|
|
builder.ToTable(table => table.HasCheckConstraint(
|
|
"ck_class_assignment_grant_period", "ends_at is null or ends_at > starts_at"));
|
|
}
|
|
}
|
|
|
|
internal sealed class EffectiveAccessProjectionConfiguration : IEntityTypeConfiguration<EffectiveAccessProjection>
|
|
{
|
|
public void Configure(EntityTypeBuilder<EffectiveAccessProjection> builder)
|
|
{
|
|
builder.ConfigureTenantEntity("effective_access_projections");
|
|
builder.Property(entity => entity.AlternateProfileVersionIds).HasColumnType("uuid[]");
|
|
builder.Property(entity => entity.ContentReleaseIds).HasColumnType("uuid[]");
|
|
builder.Property(entity => entity.AudienceSegmentIds).HasColumnType("uuid[]");
|
|
builder.Property(entity => entity.ManifestVersionIds).HasColumnType("uuid[]");
|
|
builder.HasIndex(entity => new { entity.TenantId, entity.UserId, entity.BusinessLineId }).IsUnique();
|
|
builder.HasIndex(entity => new { entity.TenantId, entity.ValidUntil });
|
|
builder.HasOne<User>().WithMany().HasForeignKey(entity => entity.UserId).OnDelete(DeleteBehavior.Cascade);
|
|
builder.HasOne<BusinessLine>().WithMany().HasForeignKey(entity => entity.BusinessLineId)
|
|
.OnDelete(DeleteBehavior.Restrict);
|
|
}
|
|
}
|