Files
tiku-backend.net/Tiku.Infrastructure/Persistence/Configurations/TenantOperationsConfigurations.cs

291 lines
14 KiB
C#

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Tiku.Domain.Catalog;
using Tiku.Domain.Identity;
using Tiku.Domain.Tenancy;
namespace Tiku.Infrastructure.Persistence.Configurations;
internal sealed class TenantAuthProviderConfiguration : IEntityTypeConfiguration<TenantAuthProvider>
{
public void Configure(EntityTypeBuilder<TenantAuthProvider> builder)
{
builder.ConfigureTenantEntity("tenant_auth_providers");
builder.ConfigureTimestamps();
builder.Property(entity => entity.Provider).HasMaxLength(50);
builder.Property(entity => entity.Status).HasSnakeCaseEnum();
builder.Property(entity => entity.DisplayName).HasMaxLength(200);
builder.Property(entity => entity.ConfigPublic).IsJson("{}");
builder.HasIndex(entity => new { entity.TenantId, entity.Provider }).IsUnique();
builder.HasIndex(entity => new { entity.TenantId, entity.Provider, entity.Status });
}
}
internal sealed class TenantSecretConfiguration : IEntityTypeConfiguration<TenantSecret>
{
public void Configure(EntityTypeBuilder<TenantSecret> builder)
{
builder.ConfigureTenantEntity("tenant_secrets");
builder.ConfigureTimestamps();
builder.Property(entity => entity.Purpose).HasMaxLength(80);
builder.Property(entity => entity.Provider).HasMaxLength(50);
builder.Property(entity => entity.SecretKey).HasMaxLength(120);
builder.Property(entity => entity.SecretRef).HasMaxLength(300);
builder.Property(entity => entity.Status).HasSnakeCaseEnum();
builder.Property(entity => entity.EncryptionKeyId).HasMaxLength(100);
builder.HasIndex(entity => new { entity.TenantId, entity.SecretRef }).IsUnique();
builder.HasIndex(entity => new { entity.TenantId, entity.Purpose, entity.Provider, entity.SecretKey }).IsUnique();
builder.HasIndex(entity => new { entity.TenantId, entity.Purpose, entity.Provider, entity.Status });
builder.ToTable(table => table.HasCheckConstraint(
"ck_tenant_secrets_encryption_envelope",
"octet_length(encrypted_payload) > 0 and octet_length(encryption_nonce) = 12 and " +
"octet_length(encryption_tag) = 16 and encryption_key_id <> ''"));
}
}
internal sealed class SmsVerificationCodeConfiguration : IEntityTypeConfiguration<SmsVerificationCode>
{
public void Configure(EntityTypeBuilder<SmsVerificationCode> builder)
{
builder.ConfigureEntity("sms_verification_codes");
builder.HasAlternateKey(entity => new { entity.TenantId, entity.Id });
builder.Property(entity => entity.Phone).HasMaxLength(32);
builder.Property(entity => entity.Purpose).HasSnakeCaseEnum();
builder.Property(entity => entity.CodeHash).HasMaxLength(256);
builder.Property(entity => entity.Provider).HasMaxLength(50);
builder.Property(entity => entity.Status).HasSnakeCaseEnum();
builder.Property(entity => entity.IpAddress).HasMaxLength(64);
builder.Property(entity => entity.UserAgent).HasMaxLength(1000);
builder.Property(entity => entity.Metadata).IsJson("{}");
builder.Property(entity => entity.CreatedAt).HasDefaultValueSql("now()");
builder.HasIndex(entity => new { entity.TenantId, entity.Phone, entity.Purpose, entity.ExpiresAt });
builder.HasIndex(entity => new { entity.TenantId, entity.Phone, entity.Purpose, entity.CreatedAt })
.HasFilter("consumed_at is null and status in ('pending', 'sent')");
builder.HasIndex(entity => new { entity.TenantId, entity.Phone, entity.Purpose })
.IsUnique()
.HasFilter("consumed_at is null and status in ('pending', 'sent')");
builder.ToTable(table =>
{
table.HasCheckConstraint("ck_sms_verification_codes_attempts", "attempts >= 0");
});
builder.HasOne<Tenant>().WithMany()
.HasForeignKey(entity => entity.TenantId)
.OnDelete(DeleteBehavior.Cascade);
}
}
internal sealed class AuthLoginEventConfiguration : IEntityTypeConfiguration<AuthLoginEvent>
{
public void Configure(EntityTypeBuilder<AuthLoginEvent> builder)
{
builder.ConfigureEntity("auth_login_events");
builder.HasAlternateKey(entity => new { entity.TenantId, entity.Id });
builder.Property(entity => entity.Provider).HasMaxLength(50);
builder.Property(entity => entity.Identifier).HasMaxLength(320);
builder.Property(entity => entity.Result).HasSnakeCaseEnum();
builder.Property(entity => entity.FailureCode).HasMaxLength(100);
builder.Property(entity => entity.IpAddress).HasMaxLength(64);
builder.Property(entity => entity.UserAgent).HasMaxLength(1000);
builder.Property(entity => entity.Metadata).IsJson("{}");
builder.Property(entity => entity.CreatedAt).HasDefaultValueSql("now()");
builder.HasIndex(entity => new { entity.TenantId, entity.UserId, entity.CreatedAt });
builder.HasOne<Tenant>().WithMany()
.HasForeignKey(entity => entity.TenantId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne<User>().WithMany()
.HasForeignKey(entity => entity.UserId)
.OnDelete(DeleteBehavior.SetNull);
}
}
internal sealed class AuthSessionConfiguration : IEntityTypeConfiguration<AuthSession>
{
public void Configure(EntityTypeBuilder<AuthSession> builder)
{
builder.ConfigureTenantEntity("auth_sessions");
builder.ConfigureTimestamps();
builder.Property(entity => entity.TokenHash).HasMaxLength(256);
builder.Property(entity => entity.Provider).HasMaxLength(50);
builder.Property(entity => entity.IpAddress).HasMaxLength(64);
builder.Property(entity => entity.UserAgent).HasMaxLength(1000);
builder.Property(entity => entity.Metadata).IsJson("{}");
builder.HasIndex(entity => entity.TokenHash).IsUnique();
builder.HasIndex(entity => new { entity.TenantId, entity.UserId, entity.ExpiresAt })
.HasFilter("revoked_at is null");
builder.HasOne<User>().WithMany()
.HasForeignKey(entity => entity.UserId)
.OnDelete(DeleteBehavior.Cascade);
}
}
internal sealed class SmsSendRateLimitConfiguration : IEntityTypeConfiguration<SmsSendRateLimit>
{
public void Configure(EntityTypeBuilder<SmsSendRateLimit> builder)
{
builder.ToTable("sms_send_rate_limits");
builder.HasKey(entity => new
{
entity.TenantId,
entity.Dimension,
entity.ScopeHash,
entity.BucketStart
});
builder.Property(entity => entity.Dimension).HasSnakeCaseEnum();
builder.Property(entity => entity.ScopeHash).HasMaxLength(128);
builder.Property(entity => entity.RequestCount).HasDefaultValue(0);
builder.Property(entity => entity.UpdatedAt).HasDefaultValueSql("now()");
builder.HasIndex(entity => entity.UpdatedAt);
builder.ToTable(table =>
{
table.HasCheckConstraint("ck_sms_send_rate_limits_request_count", "request_count >= 0");
});
builder.HasOne<Tenant>().WithMany()
.HasForeignKey(entity => entity.TenantId)
.OnDelete(DeleteBehavior.Cascade);
}
}
internal sealed class TenantRoleTemplateConfiguration : IEntityTypeConfiguration<TenantRoleTemplate>
{
public void Configure(EntityTypeBuilder<TenantRoleTemplate> builder)
{
builder.ConfigureTenantEntity("tenant_role_templates");
builder.ConfigureTimestamps();
builder.Property(entity => entity.Code).HasMaxLength(100);
builder.Property(entity => entity.Name).HasMaxLength(200);
builder.Property(entity => entity.BaseRole).HasSnakeCaseEnum();
builder.Property(entity => entity.Status).HasSnakeCaseEnum();
builder.Property(entity => entity.Permissions).IsJson("{}");
builder.Property(entity => entity.MenuPermissions).IsJson("{}");
builder.Property(entity => entity.ModulePermissions).IsJson("{}");
builder.Property(entity => entity.FieldPermissions).IsJson("{}");
builder.Property(entity => entity.DataScope).IsJson("{}");
builder.HasIndex(entity => new { entity.TenantId, entity.Code }).IsUnique();
builder.HasIndex(entity => new { entity.TenantId, entity.Status, entity.SortOrder });
builder.HasOne<User>().WithMany()
.HasForeignKey(entity => entity.CreatedBy)
.OnDelete(DeleteBehavior.SetNull);
builder.HasOne<User>().WithMany()
.HasForeignKey(entity => entity.UpdatedBy)
.OnDelete(DeleteBehavior.SetNull);
}
}
internal sealed class TenantClassConfiguration : IEntityTypeConfiguration<TenantClass>
{
public void Configure(EntityTypeBuilder<TenantClass> builder)
{
builder.ConfigureTenantEntity("tenant_classes");
builder.ConfigureTimestamps();
builder.Property(entity => entity.LegacyId).HasMaxLength(64);
builder.Property(entity => entity.Code).HasMaxLength(100);
builder.Property(entity => entity.Name).HasMaxLength(200);
builder.Property(entity => entity.Status).HasSnakeCaseEnum();
builder.Property(entity => entity.Metadata).IsJson("{}");
builder.HasIndex(entity => new { entity.TenantId, entity.LegacyId }).IsUnique();
builder.HasIndex(entity => new { entity.TenantId, entity.Code })
.IsUnique()
.HasFilter("code is not null and code <> ''");
builder.HasIndex(entity => new { entity.TenantId, entity.Status, entity.SortOrder, entity.CreatedAt });
builder.HasIndex(entity => new { entity.TenantId, entity.RegionId })
.HasFilter("region_id is not null");
builder.HasOne<Region>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.RegionId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne<User>().WithMany()
.HasForeignKey(entity => entity.CreatedBy)
.OnDelete(DeleteBehavior.SetNull);
builder.HasOne<User>().WithMany()
.HasForeignKey(entity => entity.UpdatedBy)
.OnDelete(DeleteBehavior.SetNull);
}
}
internal sealed class TenantClassMemberConfiguration : IEntityTypeConfiguration<TenantClassMember>
{
public void Configure(EntityTypeBuilder<TenantClassMember> builder)
{
builder.ConfigureTenantEntity("tenant_class_members");
builder.ConfigureTimestamps();
builder.Property(entity => entity.MemberType).HasSnakeCaseEnum();
builder.Property(entity => entity.Status).HasSnakeCaseEnum();
builder.Property(entity => entity.JoinedAt).HasDefaultValueSql("now()");
builder.Property(entity => entity.Metadata).IsJson("{}");
builder.HasIndex(entity => new { entity.TenantId, entity.ClassId, entity.UserId, entity.MemberType }).IsUnique();
builder.HasIndex(entity => new { entity.TenantId, entity.ClassId, entity.Status, entity.MemberType });
builder.HasIndex(entity => new { entity.TenantId, entity.UserId, entity.Status, entity.MemberType });
builder.HasOne<TenantClass>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.ClassId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne<User>().WithMany()
.HasForeignKey(entity => entity.UserId)
.OnDelete(DeleteBehavior.Cascade);
}
}
internal sealed class TenantStudentNoteConfiguration : IEntityTypeConfiguration<TenantStudentNote>
{
public void Configure(EntityTypeBuilder<TenantStudentNote> builder)
{
builder.ConfigureTenantEntity("tenant_student_notes");
builder.ConfigureTimestamps();
builder.Property(entity => entity.NoteType).HasSnakeCaseEnum();
builder.Property(entity => entity.Visibility).HasSnakeCaseEnum();
builder.Property(entity => entity.Metadata).IsJson("{}");
builder.HasIndex(entity => new { entity.TenantId, entity.StudentUserId, entity.IsPinned, entity.CreatedAt });
builder.HasIndex(entity => new { entity.TenantId, entity.CreatedBy, entity.CreatedAt })
.HasFilter("created_by is not null");
builder.HasOne<User>().WithMany()
.HasForeignKey(entity => entity.StudentUserId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne<User>().WithMany()
.HasForeignKey(entity => entity.CreatedBy)
.OnDelete(DeleteBehavior.SetNull);
builder.HasOne<User>().WithMany()
.HasForeignKey(entity => entity.UpdatedBy)
.OnDelete(DeleteBehavior.SetNull);
}
}
internal sealed class TenantStudentFollowupConfiguration : IEntityTypeConfiguration<TenantStudentFollowup>
{
public void Configure(EntityTypeBuilder<TenantStudentFollowup> builder)
{
builder.ConfigureTenantEntity("tenant_student_followups");
builder.ConfigureTimestamps();
builder.Property(entity => entity.Title).HasMaxLength(300);
builder.Property(entity => entity.FollowupType).HasSnakeCaseEnum();
builder.Property(entity => entity.Priority).HasSnakeCaseEnum();
builder.Property(entity => entity.Status).HasSnakeCaseEnum();
builder.Property(entity => entity.Metadata).IsJson("{}");
builder.HasIndex(entity => new { entity.TenantId, entity.StudentUserId, entity.Status, entity.DueAt, entity.CreatedAt });
builder.HasIndex(entity => new { entity.TenantId, entity.AssignedToUserId, entity.Status, entity.DueAt })
.HasFilter("assigned_to_user_id is not null");
builder.HasIndex(entity => new { entity.TenantId, entity.ClassId, entity.Status, entity.DueAt })
.HasFilter("class_id is not null");
builder.HasOne<User>().WithMany()
.HasForeignKey(entity => entity.StudentUserId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne<User>().WithMany()
.HasForeignKey(entity => entity.AssignedToUserId)
.OnDelete(DeleteBehavior.SetNull);
builder.HasOne<TenantClass>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.ClassId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.SetNull);
builder.HasOne<User>().WithMany()
.HasForeignKey(entity => entity.CompletedBy)
.OnDelete(DeleteBehavior.SetNull);
}
}