forked from xiongyuxing/tiku-backend.net
feat: add auth and tenant operations persistence
This commit is contained in:
@@ -0,0 +1,268 @@
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user