feat: harden SaaS authentication and authorization

This commit is contained in:
2026-07-28 12:15:51 +08:00
parent f22f329d33
commit 5d2248efee
123 changed files with 9090 additions and 2822 deletions

View File

@@ -13,7 +13,7 @@ using Tiku.Infrastructure.Persistence;
namespace Tiku.Infrastructure.Persistence.Migrations
{
[DbContext(typeof(TikuDbContext))]
[Migration("20260728014412_InitialSchema")]
[Migration("20260728031410_InitialSchema")]
partial class InitialSchema
{
/// <inheritdoc />
@@ -28,6 +28,110 @@ namespace Tiku.Infrastructure.Persistence.Migrations
NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "ltree");
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("FriendlyName")
.HasColumnType("text")
.HasColumnName("friendly_name");
b.Property<string>("Xml")
.HasColumnType("text")
.HasColumnName("xml");
b.HasKey("Id")
.HasName("pk_data_protection_keys");
b.ToTable("data_protection_keys", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("text")
.HasColumnName("claim_type");
b.Property<string>("ClaimValue")
.HasColumnType("text")
.HasColumnName("claim_value");
b.Property<Guid>("UserId")
.HasColumnType("uuid")
.HasColumnName("user_id");
b.HasKey("Id")
.HasName("pk_user_claims");
b.HasIndex("UserId")
.HasDatabaseName("ix_user_claims_user_id");
b.ToTable("user_claims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("text")
.HasColumnName("login_provider");
b.Property<string>("ProviderKey")
.HasColumnType("text")
.HasColumnName("provider_key");
b.Property<string>("ProviderDisplayName")
.HasColumnType("text")
.HasColumnName("provider_display_name");
b.Property<Guid>("UserId")
.HasColumnType("uuid")
.HasColumnName("user_id");
b.HasKey("LoginProvider", "ProviderKey")
.HasName("pk_user_logins");
b.HasIndex("UserId")
.HasDatabaseName("ix_user_logins_user_id");
b.ToTable("user_logins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
{
b.Property<Guid>("UserId")
.HasColumnType("uuid")
.HasColumnName("user_id");
b.Property<string>("LoginProvider")
.HasColumnType("text")
.HasColumnName("login_provider");
b.Property<string>("Name")
.HasColumnType("text")
.HasColumnName("name");
b.Property<string>("Value")
.HasColumnType("text")
.HasColumnName("value");
b.HasKey("UserId", "LoginProvider", "Name")
.HasName("pk_user_tokens");
b.ToTable("user_tokens", (string)null);
});
modelBuilder.Entity("Tiku.Domain.Catalog.Category", b =>
{
b.Property<Guid>("Id")
@@ -7918,11 +8022,21 @@ namespace Tiku.Infrastructure.Persistence.Migrations
.HasColumnName("id")
.HasDefaultValueSql("gen_random_uuid()");
b.Property<int>("AccessFailedCount")
.HasColumnType("integer")
.HasColumnName("access_failed_count");
b.Property<string>("AvatarUrl")
.HasMaxLength(2048)
.HasColumnType("character varying(2048)")
.HasColumnName("avatar_url");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasMaxLength(64)
.HasColumnType("character varying(64)")
.HasColumnName("concurrency_stamp");
b.Property<DateTimeOffset>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
@@ -7934,6 +8048,14 @@ namespace Tiku.Infrastructure.Persistence.Migrations
.HasColumnType("citext")
.HasColumnName("email");
b.Property<bool>("EmailConfirmed")
.HasColumnType("boolean")
.HasColumnName("email_confirmed");
b.Property<bool>("ForcePasswordChange")
.HasColumnType("boolean")
.HasColumnName("force_password_change");
b.Property<DateTimeOffset?>("LastSeenAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("last_seen_at");
@@ -7943,25 +8065,48 @@ namespace Tiku.Infrastructure.Persistence.Migrations
.HasColumnType("character varying(64)")
.HasColumnName("legacy_id");
b.Property<string>("LegacyPasswordHash")
.HasMaxLength(512)
.HasColumnType("character varying(512)")
.HasColumnName("legacy_password_hash");
b.Property<bool>("LockoutEnabled")
.HasColumnType("boolean")
.HasColumnName("lockout_enabled");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("timestamp with time zone")
.HasColumnName("lockout_end");
b.Property<string>("Name")
.HasMaxLength(200)
.HasColumnType("character varying(200)")
.HasColumnName("name");
b.Property<bool>("PasswordMigrationRequired")
.HasColumnType("boolean")
.HasColumnName("password_migration_required");
b.Property<string>("NormalizedEmail")
.HasMaxLength(320)
.HasColumnType("character varying(320)")
.HasColumnName("normalized_email");
b.Property<string>("NormalizedUserName")
.HasMaxLength(100)
.HasColumnType("character varying(100)")
.HasColumnName("normalized_user_name");
b.Property<string>("PasswordHash")
.HasMaxLength(1024)
.HasColumnType("character varying(1024)")
.HasColumnName("password_hash");
b.Property<string>("Phone")
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("phone");
b.Property<string>("PhoneNumber")
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("phone_number");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("boolean")
.HasColumnName("phone_number_confirmed");
b.Property<string>("PrimaryRole")
.IsRequired()
.HasMaxLength(50)
@@ -7978,36 +8123,50 @@ namespace Tiku.Infrastructure.Persistence.Migrations
.HasColumnType("integer")
.HasColumnName("score");
b.Property<string>("SecurityStamp")
.HasMaxLength(64)
.HasColumnType("character varying(64)")
.HasColumnName("security_stamp");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("status");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("boolean")
.HasColumnName("two_factor_enabled");
b.Property<DateTimeOffset>("UpdatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at")
.HasDefaultValueSql("now()");
b.Property<string>("Username")
b.Property<string>("UserName")
.HasMaxLength(100)
.HasColumnType("character varying(100)")
.HasColumnName("username");
.HasColumnName("user_name");
b.HasKey("Id")
.HasName("pk_users");
b.HasIndex("Email")
.IsUnique()
.HasDatabaseName("ix_users_email");
b.HasIndex("LegacyId")
.IsUnique()
.HasDatabaseName("ix_users_legacy_id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("email_index");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("user_name_index");
b.HasIndex("Phone")
.IsUnique()
.HasDatabaseName("ix_users_phone");
b.HasIndex("Username")
.IsUnique()
.HasDatabaseName("ix_users_username");
b.ToTable("users", (string)null);
});
@@ -8052,12 +8211,6 @@ namespace Tiku.Infrastructure.Persistence.Migrations
.HasColumnType("character varying(255)")
.HasColumnName("provider_subject");
b.Property<JsonElement>("SecretPayload")
.ValueGeneratedOnAdd()
.HasColumnType("jsonb")
.HasColumnName("secret_payload")
.HasDefaultValueSql("'{}'::jsonb");
b.Property<string>("UnionId")
.HasMaxLength(255)
.HasColumnType("character varying(255)")
@@ -12651,6 +12804,95 @@ namespace Tiku.Infrastructure.Persistence.Migrations
b.ToTable("question_versions", (string)null);
});
modelBuilder.Entity("Tiku.Domain.Tenancy.AuthChallenge", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("id")
.HasDefaultValueSql("gen_random_uuid()");
b.Property<DateTimeOffset?>("ConsumedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("consumed_at");
b.Property<DateTimeOffset>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasDefaultValueSql("now()");
b.Property<DateTimeOffset>("ExpiresAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("expires_at");
b.Property<string>("IpAddress")
.HasMaxLength(100)
.HasColumnType("character varying(100)")
.HasColumnName("ip_address");
b.Property<string>("Provider")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("provider");
b.Property<string>("Purpose")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("purpose");
b.Property<string>("Realm")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("realm");
b.Property<string>("SecurityStamp")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("character varying(128)")
.HasColumnName("security_stamp");
b.Property<Guid?>("TenantId")
.HasColumnType("uuid")
.HasColumnName("tenant_id");
b.Property<string>("TokenHash")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)")
.HasColumnName("token_hash");
b.Property<string>("UserAgent")
.HasMaxLength(1024)
.HasColumnType("character varying(1024)")
.HasColumnName("user_agent");
b.Property<Guid>("UserId")
.HasColumnType("uuid")
.HasColumnName("user_id");
b.HasKey("Id")
.HasName("pk_auth_challenges");
b.HasIndex("TenantId")
.HasDatabaseName("ix_auth_challenges_tenant_id");
b.HasIndex("TokenHash")
.IsUnique()
.HasDatabaseName("ix_auth_challenges_token_hash");
b.HasIndex("UserId", "Purpose", "ExpiresAt")
.HasDatabaseName("ix_auth_challenges_user_id_purpose_expires_at");
b.ToTable("auth_challenges", null, t =>
{
t.HasCheckConstraint("ck_auth_challenges_realm_tenant", "(realm = 'tenant' and tenant_id is not null) or (realm = 'platform' and tenant_id is null)");
});
});
modelBuilder.Entity("Tiku.Domain.Tenancy.AuthLoginEvent", b =>
{
b.Property<Guid>("Id")
@@ -12755,20 +12997,53 @@ namespace Tiku.Infrastructure.Persistence.Migrations
.HasColumnName("metadata")
.HasDefaultValueSql("'{}'::jsonb");
b.Property<bool>("MfaSatisfied")
.HasColumnType("boolean")
.HasColumnName("mfa_satisfied");
b.Property<Guid?>("ParentSessionId")
.HasColumnType("uuid")
.HasColumnName("parent_session_id");
b.Property<string>("Provider")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("provider");
b.Property<string>("Realm")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("realm");
b.Property<Guid?>("ReplacedBySessionId")
.HasColumnType("uuid")
.HasColumnName("replaced_by_session_id");
b.Property<DateTimeOffset?>("RevokedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("revoked_at");
b.Property<Guid>("TenantId")
b.Property<string>("RevokedReason")
.HasMaxLength(100)
.HasColumnType("character varying(100)")
.HasColumnName("revoked_reason");
b.Property<string>("SecurityStamp")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("character varying(128)")
.HasColumnName("security_stamp");
b.Property<Guid?>("TenantId")
.HasColumnType("uuid")
.HasColumnName("tenant_id");
b.Property<Guid>("TokenFamilyId")
.HasColumnType("uuid")
.HasColumnName("token_family_id");
b.Property<string>("TokenHash")
.IsRequired()
.HasMaxLength(256)
@@ -12793,8 +13068,8 @@ namespace Tiku.Infrastructure.Persistence.Migrations
b.HasKey("Id")
.HasName("pk_auth_sessions");
b.HasAlternateKey("TenantId", "Id")
.HasName("ak_auth_sessions_tenant_id_id");
b.HasIndex("TenantId")
.HasDatabaseName("ix_auth_sessions_tenant_id");
b.HasIndex("TokenHash")
.IsUnique()
@@ -12804,11 +13079,17 @@ namespace Tiku.Infrastructure.Persistence.Migrations
b.HasIndex("UserId")
.HasDatabaseName("ix_auth_sessions_user_id");
b.HasIndex("TenantId", "UserId", "ExpiresAt")
.HasDatabaseName("ix_auth_sessions_tenant_id_user_id_expires_at")
b.HasIndex("TokenFamilyId", "RevokedAt")
.HasDatabaseName("ix_auth_sessions_token_family_id_revoked_at");
b.HasIndex("Realm", "TenantId", "UserId", "ExpiresAt")
.HasDatabaseName("ix_auth_sessions_realm_tenant_id_user_id_expires_at")
.HasFilter("revoked_at is null");
b.ToTable("auth_sessions", (string)null);
b.ToTable("auth_sessions", null, t =>
{
t.HasCheckConstraint("ck_auth_sessions_realm_tenant", "(realm = 'tenant' and tenant_id is not null) or (realm = 'platform' and tenant_id is null)");
});
});
modelBuilder.Entity("Tiku.Domain.Tenancy.SmsSendRateLimit", b =>
@@ -12934,9 +13215,6 @@ namespace Tiku.Infrastructure.Persistence.Migrations
b.HasKey("Id")
.HasName("pk_sms_verification_codes");
b.HasAlternateKey("TenantId", "Id")
.HasName("ak_sms_verification_codes_tenant_id_id");
b.HasIndex("TenantId", "Phone", "Purpose")
.IsUnique()
.HasDatabaseName("ix_sms_verification_codes_tenant_id_phone_purpose")
@@ -13626,22 +13904,12 @@ namespace Tiku.Infrastructure.Persistence.Migrations
.HasColumnType("character varying(50)")
.HasColumnName("legacy_role");
b.Property<JsonElement>("Permissions")
.ValueGeneratedOnAdd()
.HasColumnType("jsonb")
.HasColumnName("permissions")
.HasDefaultValueSql("'{}'::jsonb");
b.Property<string>("Role")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("role");
b.Property<Guid?>("RoleTemplateId")
.HasColumnType("uuid")
.HasColumnName("role_template_id");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(32)
@@ -13671,9 +13939,6 @@ namespace Tiku.Infrastructure.Persistence.Migrations
b.HasIndex("UserId")
.HasDatabaseName("ix_tenant_memberships_user_id");
b.HasIndex("TenantId", "RoleTemplateId")
.HasDatabaseName("ix_tenant_memberships_tenant_id_role_template_id");
b.HasIndex("TenantId", "UserId", "Role")
.IsUnique()
.HasDatabaseName("ix_tenant_memberships_tenant_id_user_id_role");
@@ -13681,126 +13946,6 @@ namespace Tiku.Infrastructure.Persistence.Migrations
b.ToTable("tenant_memberships", (string)null);
});
modelBuilder.Entity("Tiku.Domain.Tenancy.TenantRoleTemplate", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("id")
.HasDefaultValueSql("gen_random_uuid()");
b.Property<string>("BaseRole")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("base_role");
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)")
.HasColumnName("code");
b.Property<DateTimeOffset>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasDefaultValueSql("now()");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uuid")
.HasColumnName("created_by");
b.Property<JsonElement>("DataScope")
.ValueGeneratedOnAdd()
.HasColumnType("jsonb")
.HasColumnName("data_scope")
.HasDefaultValueSql("'{}'::jsonb");
b.Property<string>("Description")
.HasColumnType("text")
.HasColumnName("description");
b.Property<JsonElement>("FieldPermissions")
.ValueGeneratedOnAdd()
.HasColumnType("jsonb")
.HasColumnName("field_permissions")
.HasDefaultValueSql("'{}'::jsonb");
b.Property<bool>("IsSystem")
.HasColumnType("boolean")
.HasColumnName("is_system");
b.Property<JsonElement>("MenuPermissions")
.ValueGeneratedOnAdd()
.HasColumnType("jsonb")
.HasColumnName("menu_permissions")
.HasDefaultValueSql("'{}'::jsonb");
b.Property<JsonElement>("ModulePermissions")
.ValueGeneratedOnAdd()
.HasColumnType("jsonb")
.HasColumnName("module_permissions")
.HasDefaultValueSql("'{}'::jsonb");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)")
.HasColumnName("name");
b.Property<JsonElement>("Permissions")
.ValueGeneratedOnAdd()
.HasColumnType("jsonb")
.HasColumnName("permissions")
.HasDefaultValueSql("'{}'::jsonb");
b.Property<int>("SortOrder")
.HasColumnType("integer")
.HasColumnName("sort_order");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("status");
b.Property<Guid>("TenantId")
.HasColumnType("uuid")
.HasColumnName("tenant_id");
b.Property<DateTimeOffset>("UpdatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at")
.HasDefaultValueSql("now()");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uuid")
.HasColumnName("updated_by");
b.HasKey("Id")
.HasName("pk_tenant_role_templates");
b.HasAlternateKey("TenantId", "Id")
.HasName("ak_tenant_role_templates_tenant_id_id");
b.HasIndex("CreatedBy")
.HasDatabaseName("ix_tenant_role_templates_created_by");
b.HasIndex("UpdatedBy")
.HasDatabaseName("ix_tenant_role_templates_updated_by");
b.HasIndex("TenantId", "Code")
.IsUnique()
.HasDatabaseName("ix_tenant_role_templates_tenant_id_code");
b.HasIndex("TenantId", "Status", "SortOrder")
.HasDatabaseName("ix_tenant_role_templates_tenant_id_status_sort_order");
b.ToTable("tenant_role_templates", (string)null);
});
modelBuilder.Entity("Tiku.Domain.Tenancy.TenantSecret", b =>
{
b.Property<Guid>("Id")
@@ -14156,6 +14301,36 @@ namespace Tiku.Infrastructure.Persistence.Migrations
b.ToTable("tenant_student_notes", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
{
b.HasOne("Tiku.Domain.Identity.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_user_claims_users_user_id");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
{
b.HasOne("Tiku.Domain.Identity.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_user_logins_users_user_id");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
{
b.HasOne("Tiku.Domain.Identity.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_user_tokens_users_user_id");
});
modelBuilder.Entity("Tiku.Domain.Catalog.Category", b =>
{
b.HasOne("Tiku.Domain.Tenancy.Tenant", null)
@@ -17207,12 +17382,28 @@ namespace Tiku.Infrastructure.Persistence.Migrations
.HasConstraintName("fk_question_versions_questions_tenant_id_question_id");
});
modelBuilder.Entity("Tiku.Domain.Tenancy.AuthLoginEvent", b =>
modelBuilder.Entity("Tiku.Domain.Tenancy.AuthChallenge", b =>
{
b.HasOne("Tiku.Domain.Tenancy.Tenant", null)
.WithMany()
.HasForeignKey("TenantId")
.OnDelete(DeleteBehavior.Cascade)
.HasConstraintName("fk_auth_challenges_tenants_tenant_id");
b.HasOne("Tiku.Domain.Identity.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_auth_challenges_users_user_id");
});
modelBuilder.Entity("Tiku.Domain.Tenancy.AuthLoginEvent", b =>
{
b.HasOne("Tiku.Domain.Tenancy.Tenant", null)
.WithMany()
.HasForeignKey("TenantId")
.OnDelete(DeleteBehavior.SetNull)
.IsRequired()
.HasConstraintName("fk_auth_login_events_tenants_tenant_id");
@@ -17229,7 +17420,6 @@ namespace Tiku.Infrastructure.Persistence.Migrations
.WithMany()
.HasForeignKey("TenantId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_auth_sessions_tenants_tenant_id");
b.HasOne("Tiku.Domain.Identity.User", null)
@@ -17378,35 +17568,6 @@ namespace Tiku.Infrastructure.Persistence.Migrations
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_tenant_memberships_users_user_id");
b.HasOne("Tiku.Domain.Tenancy.TenantRoleTemplate", null)
.WithMany()
.HasForeignKey("TenantId", "RoleTemplateId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Restrict)
.HasConstraintName("fk_tenant_memberships_tenant_role_templates_tenant_id_role_tem~");
});
modelBuilder.Entity("Tiku.Domain.Tenancy.TenantRoleTemplate", b =>
{
b.HasOne("Tiku.Domain.Identity.User", null)
.WithMany()
.HasForeignKey("CreatedBy")
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("fk_tenant_role_templates_users_created_by");
b.HasOne("Tiku.Domain.Tenancy.Tenant", null)
.WithMany()
.HasForeignKey("TenantId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_tenant_role_templates_tenants_tenant_id");
b.HasOne("Tiku.Domain.Identity.User", null)
.WithMany()
.HasForeignKey("UpdatedBy")
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("fk_tenant_role_templates_users_updated_by");
});
modelBuilder.Entity("Tiku.Domain.Tenancy.TenantSecret", b =>

View File

@@ -1,7 +1,7 @@
using System;
using System.Text.Json;
using Microsoft.EntityFrameworkCore.Migrations;
using Tiku.Infrastructure.Persistence;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
@@ -38,6 +38,20 @@ namespace Tiku.Infrastructure.Persistence.Migrations
table.UniqueConstraint("ak_backend_permissions_code", x => x.code);
});
migrationBuilder.CreateTable(
name: "data_protection_keys",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
friendly_name = table.Column<string>(type: "text", nullable: true),
xml = table.Column<string>(type: "text", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_data_protection_keys", x => x.id);
});
migrationBuilder.CreateTable(
name: "platform_backend_roles",
columns: table => new
@@ -137,19 +151,31 @@ namespace Tiku.Infrastructure.Persistence.Migrations
{
id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"),
legacy_id = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
username = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: true),
email = table.Column<string>(type: "citext", maxLength: 320, nullable: true),
phone = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
name = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: true),
avatar_url = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: true),
primary_role = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
score = table.Column<int>(type: "integer", nullable: false),
last_seen_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
legacy_password_hash = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: true),
password_migration_required = table.Column<bool>(type: "boolean", nullable: false),
status = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
force_password_change = table.Column<bool>(type: "boolean", nullable: false),
raw_profile = table.Column<JsonElement>(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"),
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()")
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
user_name = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: true),
normalized_user_name = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: true),
email = table.Column<string>(type: "citext", maxLength: 320, nullable: true),
normalized_email = table.Column<string>(type: "character varying(320)", maxLength: 320, nullable: true),
email_confirmed = table.Column<bool>(type: "boolean", nullable: false),
password_hash = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
security_stamp = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
concurrency_stamp = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
phone_number = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
phone_number_confirmed = table.Column<bool>(type: "boolean", nullable: false),
two_factor_enabled = table.Column<bool>(type: "boolean", nullable: false),
lockout_end = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
lockout_enabled = table.Column<bool>(type: "boolean", nullable: false),
access_failed_count = table.Column<int>(type: "integer", nullable: false)
},
constraints: table =>
{
@@ -265,6 +291,27 @@ namespace Tiku.Infrastructure.Persistence.Migrations
onDelete: ReferentialAction.SetNull);
});
migrationBuilder.CreateTable(
name: "user_claims",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
user_id = table.Column<Guid>(type: "uuid", nullable: false),
claim_type = table.Column<string>(type: "text", nullable: true),
claim_value = table.Column<string>(type: "text", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_user_claims", x => x.id);
table.ForeignKey(
name: "fk_user_claims_users_user_id",
column: x => x.user_id,
principalTable: "users",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "user_identities",
columns: table => new
@@ -277,7 +324,6 @@ namespace Tiku.Infrastructure.Persistence.Migrations
open_id = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: true),
phone = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
email = table.Column<string>(type: "citext", maxLength: 320, nullable: true),
secret_payload = table.Column<JsonElement>(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"),
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()")
},
@@ -292,6 +338,46 @@ namespace Tiku.Infrastructure.Persistence.Migrations
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "user_logins",
columns: table => new
{
login_provider = table.Column<string>(type: "text", nullable: false),
provider_key = table.Column<string>(type: "text", nullable: false),
provider_display_name = table.Column<string>(type: "text", nullable: true),
user_id = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_user_logins", x => new { x.login_provider, x.provider_key });
table.ForeignKey(
name: "fk_user_logins_users_user_id",
column: x => x.user_id,
principalTable: "users",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "user_tokens",
columns: table => new
{
user_id = table.Column<Guid>(type: "uuid", nullable: false),
login_provider = table.Column<string>(type: "text", nullable: false),
name = table.Column<string>(type: "text", nullable: false),
value = table.Column<string>(type: "text", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_user_tokens", x => new { x.user_id, x.login_provider, x.name });
table.ForeignKey(
name: "fk_user_tokens_users_user_id",
column: x => x.user_id,
principalTable: "users",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "platform_backend_role_menus",
columns: table => new
@@ -403,6 +489,42 @@ namespace Tiku.Infrastructure.Persistence.Migrations
onDelete: ReferentialAction.SetNull);
});
migrationBuilder.CreateTable(
name: "auth_challenges",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"),
user_id = table.Column<Guid>(type: "uuid", nullable: false),
realm = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
tenant_id = table.Column<Guid>(type: "uuid", nullable: true),
purpose = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
token_hash = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
security_stamp = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
provider = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
expires_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
consumed_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
ip_address = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: true),
user_agent = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()")
},
constraints: table =>
{
table.PrimaryKey("pk_auth_challenges", x => x.id);
table.CheckConstraint("ck_auth_challenges_realm_tenant", "(realm = 'tenant' and tenant_id is not null) or (realm = 'platform' and tenant_id is null)");
table.ForeignKey(
name: "fk_auth_challenges_tenants_tenant_id",
column: x => x.tenant_id,
principalTable: "tenants",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_auth_challenges_users_user_id",
column: x => x.user_id,
principalTable: "users",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "auth_login_events",
columns: table => new
@@ -428,7 +550,7 @@ namespace Tiku.Infrastructure.Persistence.Migrations
column: x => x.tenant_id,
principalTable: "tenants",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
onDelete: ReferentialAction.SetNull);
table.ForeignKey(
name: "fk_auth_login_events_users_user_id",
column: x => x.user_id,
@@ -442,22 +564,29 @@ namespace Tiku.Infrastructure.Persistence.Migrations
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"),
realm = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
tenant_id = table.Column<Guid>(type: "uuid", nullable: true),
user_id = table.Column<Guid>(type: "uuid", nullable: false),
token_family_id = table.Column<Guid>(type: "uuid", nullable: false),
parent_session_id = table.Column<Guid>(type: "uuid", nullable: true),
replaced_by_session_id = table.Column<Guid>(type: "uuid", nullable: true),
token_hash = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
security_stamp = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
mfa_satisfied = table.Column<bool>(type: "boolean", nullable: false),
provider = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
expires_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
revoked_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
revoked_reason = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: true),
ip_address = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: true),
user_agent = table.Column<string>(type: "character varying(1000)", maxLength: 1000, nullable: true),
metadata = table.Column<JsonElement>(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()")
},
constraints: table =>
{
table.PrimaryKey("pk_auth_sessions", x => x.id);
table.UniqueConstraint("ak_auth_sessions_tenant_id_id", x => new { x.tenant_id, x.id });
table.CheckConstraint("ck_auth_sessions_realm_tenant", "(realm = 'tenant' and tenant_id is not null) or (realm = 'platform' and tenant_id is null)");
table.ForeignKey(
name: "fk_auth_sessions_tenants_tenant_id",
column: x => x.tenant_id,
@@ -1186,7 +1315,6 @@ namespace Tiku.Infrastructure.Persistence.Migrations
constraints: table =>
{
table.PrimaryKey("pk_sms_verification_codes", x => x.id);
table.UniqueConstraint("ak_sms_verification_codes_tenant_id_id", x => new { x.tenant_id, x.id });
table.CheckConstraint("ck_sms_verification_codes_attempts", "attempts >= 0");
table.ForeignKey(
name: "fk_sms_verification_codes_tenants_tenant_id",
@@ -1498,50 +1626,34 @@ namespace Tiku.Infrastructure.Persistence.Migrations
});
migrationBuilder.CreateTable(
name: "tenant_role_templates",
name: "tenant_memberships",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"),
code = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
name = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
description = table.Column<string>(type: "text", nullable: true),
base_role = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
user_id = table.Column<Guid>(type: "uuid", nullable: false),
role = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
status = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
permissions = table.Column<JsonElement>(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"),
menu_permissions = table.Column<JsonElement>(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"),
module_permissions = table.Column<JsonElement>(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"),
field_permissions = table.Column<JsonElement>(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"),
data_scope = table.Column<JsonElement>(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"),
is_system = table.Column<bool>(type: "boolean", nullable: false),
sort_order = table.Column<int>(type: "integer", nullable: false),
created_by = table.Column<Guid>(type: "uuid", nullable: true),
updated_by = table.Column<Guid>(type: "uuid", nullable: true),
legacy_role = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()")
},
constraints: table =>
{
table.PrimaryKey("pk_tenant_role_templates", x => x.id);
table.UniqueConstraint("ak_tenant_role_templates_tenant_id_id", x => new { x.tenant_id, x.id });
table.PrimaryKey("pk_tenant_memberships", x => x.id);
table.UniqueConstraint("ak_tenant_memberships_tenant_id_id", x => new { x.tenant_id, x.id });
table.ForeignKey(
name: "fk_tenant_role_templates_tenants_tenant_id",
name: "fk_tenant_memberships_tenants_tenant_id",
column: x => x.tenant_id,
principalTable: "tenants",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_tenant_role_templates_users_created_by",
column: x => x.created_by,
name: "fk_tenant_memberships_users_user_id",
column: x => x.user_id,
principalTable: "users",
principalColumn: "id",
onDelete: ReferentialAction.SetNull);
table.ForeignKey(
name: "fk_tenant_role_templates_users_updated_by",
column: x => x.updated_by,
principalTable: "users",
principalColumn: "id",
onDelete: ReferentialAction.SetNull);
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
@@ -2885,45 +2997,6 @@ namespace Tiku.Infrastructure.Persistence.Migrations
onDelete: ReferentialAction.SetNull);
});
migrationBuilder.CreateTable(
name: "tenant_memberships",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"),
user_id = table.Column<Guid>(type: "uuid", nullable: false),
role_template_id = table.Column<Guid>(type: "uuid", nullable: true),
role = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
status = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
permissions = table.Column<JsonElement>(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"),
legacy_role = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()")
},
constraints: table =>
{
table.PrimaryKey("pk_tenant_memberships", x => x.id);
table.UniqueConstraint("ak_tenant_memberships_tenant_id_id", x => new { x.tenant_id, x.id });
table.ForeignKey(
name: "fk_tenant_memberships_tenant_role_templates_tenant_id_role_tem~",
columns: x => new { x.tenant_id, x.role_template_id },
principalTable: "tenant_role_templates",
principalColumns: new[] { "tenant_id", "id" },
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "fk_tenant_memberships_tenants_tenant_id",
column: x => x.tenant_id,
principalTable: "tenants",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_tenant_memberships_users_user_id",
column: x => x.user_id,
principalTable: "users",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "content_nodes",
columns: table => new
@@ -6392,6 +6465,22 @@ namespace Tiku.Infrastructure.Persistence.Migrations
table: "audit_logs",
columns: new[] { "tenant_id", "target_type", "target_id", "created_at" });
migrationBuilder.CreateIndex(
name: "ix_auth_challenges_tenant_id",
table: "auth_challenges",
column: "tenant_id");
migrationBuilder.CreateIndex(
name: "ix_auth_challenges_token_hash",
table: "auth_challenges",
column: "token_hash",
unique: true);
migrationBuilder.CreateIndex(
name: "ix_auth_challenges_user_id_purpose_expires_at",
table: "auth_challenges",
columns: new[] { "user_id", "purpose", "expires_at" });
migrationBuilder.CreateIndex(
name: "ix_auth_login_events_tenant_id_user_id_created_at",
table: "auth_login_events",
@@ -6403,11 +6492,21 @@ namespace Tiku.Infrastructure.Persistence.Migrations
column: "user_id");
migrationBuilder.CreateIndex(
name: "ix_auth_sessions_tenant_id_user_id_expires_at",
name: "ix_auth_sessions_realm_tenant_id_user_id_expires_at",
table: "auth_sessions",
columns: new[] { "tenant_id", "user_id", "expires_at" },
columns: new[] { "realm", "tenant_id", "user_id", "expires_at" },
filter: "revoked_at is null");
migrationBuilder.CreateIndex(
name: "ix_auth_sessions_tenant_id",
table: "auth_sessions",
column: "tenant_id");
migrationBuilder.CreateIndex(
name: "ix_auth_sessions_token_family_id_revoked_at",
table: "auth_sessions",
columns: new[] { "token_family_id", "revoked_at" });
migrationBuilder.CreateIndex(
name: "ix_auth_sessions_token_hash",
table: "auth_sessions",
@@ -8609,11 +8708,6 @@ namespace Tiku.Infrastructure.Persistence.Migrations
table: "tenant_invoices",
columns: new[] { "tenant_id", "status", "due_date" });
migrationBuilder.CreateIndex(
name: "ix_tenant_memberships_tenant_id_role_template_id",
table: "tenant_memberships",
columns: new[] { "tenant_id", "role_template_id" });
migrationBuilder.CreateIndex(
name: "ix_tenant_memberships_tenant_id_user_id_role",
table: "tenant_memberships",
@@ -8656,27 +8750,6 @@ namespace Tiku.Infrastructure.Persistence.Migrations
table: "tenant_question_references",
columns: new[] { "question_owner_tenant_id", "question_id" });
migrationBuilder.CreateIndex(
name: "ix_tenant_role_templates_created_by",
table: "tenant_role_templates",
column: "created_by");
migrationBuilder.CreateIndex(
name: "ix_tenant_role_templates_tenant_id_code",
table: "tenant_role_templates",
columns: new[] { "tenant_id", "code" },
unique: true);
migrationBuilder.CreateIndex(
name: "ix_tenant_role_templates_tenant_id_status_sort_order",
table: "tenant_role_templates",
columns: new[] { "tenant_id", "status", "sort_order" });
migrationBuilder.CreateIndex(
name: "ix_tenant_role_templates_updated_by",
table: "tenant_role_templates",
column: "updated_by");
migrationBuilder.CreateIndex(
name: "ix_tenant_secrets_tenant_id_purpose_provider_secret_key",
table: "tenant_secrets",
@@ -8850,6 +8923,11 @@ namespace Tiku.Infrastructure.Persistence.Migrations
table: "user_badges",
column: "user_id");
migrationBuilder.CreateIndex(
name: "ix_user_claims_user_id",
table: "user_claims",
column: "user_id");
migrationBuilder.CreateIndex(
name: "ix_user_identities_provider_provider_subject",
table: "user_identities",
@@ -8861,6 +8939,11 @@ namespace Tiku.Infrastructure.Persistence.Migrations
table: "user_identities",
column: "user_id");
migrationBuilder.CreateIndex(
name: "ix_user_logins_user_id",
table: "user_logins",
column: "user_id");
migrationBuilder.CreateIndex(
name: "ix_user_notifications_created_by",
table: "user_notifications",
@@ -8942,10 +9025,9 @@ namespace Tiku.Infrastructure.Persistence.Migrations
column: "user_id");
migrationBuilder.CreateIndex(
name: "ix_users_email",
name: "email_index",
table: "users",
column: "email",
unique: true);
column: "normalized_email");
migrationBuilder.CreateIndex(
name: "ix_users_legacy_id",
@@ -8960,9 +9042,9 @@ namespace Tiku.Infrastructure.Persistence.Migrations
unique: true);
migrationBuilder.CreateIndex(
name: "ix_users_username",
name: "user_name_index",
table: "users",
column: "username",
column: "normalized_user_name",
unique: true);
migrationBuilder.CreateIndex(
@@ -9347,6 +9429,9 @@ namespace Tiku.Infrastructure.Persistence.Migrations
migrationBuilder.DropTable(
name: "app_assets");
migrationBuilder.DropTable(
name: "auth_challenges");
migrationBuilder.DropTable(
name: "auth_login_events");
@@ -9395,6 +9480,9 @@ namespace Tiku.Infrastructure.Persistence.Migrations
migrationBuilder.DropTable(
name: "dashboard_daily_stats");
migrationBuilder.DropTable(
name: "data_protection_keys");
migrationBuilder.DropTable(
name: "entitlements");
@@ -9578,15 +9666,24 @@ namespace Tiku.Infrastructure.Persistence.Migrations
migrationBuilder.DropTable(
name: "user_badges");
migrationBuilder.DropTable(
name: "user_claims");
migrationBuilder.DropTable(
name: "user_identities");
migrationBuilder.DropTable(
name: "user_logins");
migrationBuilder.DropTable(
name: "user_notifications");
migrationBuilder.DropTable(
name: "user_score_events");
migrationBuilder.DropTable(
name: "user_tokens");
migrationBuilder.DropTable(
name: "user_word_favorites");
@@ -9662,9 +9759,6 @@ namespace Tiku.Infrastructure.Persistence.Migrations
migrationBuilder.DropTable(
name: "tenant_backend_roles");
migrationBuilder.DropTable(
name: "tenant_role_templates");
migrationBuilder.DropTable(
name: "tenant_classes");

View File

@@ -25,6 +25,110 @@ namespace Tiku.Infrastructure.Persistence.Migrations
NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "ltree");
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("FriendlyName")
.HasColumnType("text")
.HasColumnName("friendly_name");
b.Property<string>("Xml")
.HasColumnType("text")
.HasColumnName("xml");
b.HasKey("Id")
.HasName("pk_data_protection_keys");
b.ToTable("data_protection_keys", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("text")
.HasColumnName("claim_type");
b.Property<string>("ClaimValue")
.HasColumnType("text")
.HasColumnName("claim_value");
b.Property<Guid>("UserId")
.HasColumnType("uuid")
.HasColumnName("user_id");
b.HasKey("Id")
.HasName("pk_user_claims");
b.HasIndex("UserId")
.HasDatabaseName("ix_user_claims_user_id");
b.ToTable("user_claims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("text")
.HasColumnName("login_provider");
b.Property<string>("ProviderKey")
.HasColumnType("text")
.HasColumnName("provider_key");
b.Property<string>("ProviderDisplayName")
.HasColumnType("text")
.HasColumnName("provider_display_name");
b.Property<Guid>("UserId")
.HasColumnType("uuid")
.HasColumnName("user_id");
b.HasKey("LoginProvider", "ProviderKey")
.HasName("pk_user_logins");
b.HasIndex("UserId")
.HasDatabaseName("ix_user_logins_user_id");
b.ToTable("user_logins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
{
b.Property<Guid>("UserId")
.HasColumnType("uuid")
.HasColumnName("user_id");
b.Property<string>("LoginProvider")
.HasColumnType("text")
.HasColumnName("login_provider");
b.Property<string>("Name")
.HasColumnType("text")
.HasColumnName("name");
b.Property<string>("Value")
.HasColumnType("text")
.HasColumnName("value");
b.HasKey("UserId", "LoginProvider", "Name")
.HasName("pk_user_tokens");
b.ToTable("user_tokens", (string)null);
});
modelBuilder.Entity("Tiku.Domain.Catalog.Category", b =>
{
b.Property<Guid>("Id")
@@ -7915,11 +8019,21 @@ namespace Tiku.Infrastructure.Persistence.Migrations
.HasColumnName("id")
.HasDefaultValueSql("gen_random_uuid()");
b.Property<int>("AccessFailedCount")
.HasColumnType("integer")
.HasColumnName("access_failed_count");
b.Property<string>("AvatarUrl")
.HasMaxLength(2048)
.HasColumnType("character varying(2048)")
.HasColumnName("avatar_url");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasMaxLength(64)
.HasColumnType("character varying(64)")
.HasColumnName("concurrency_stamp");
b.Property<DateTimeOffset>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
@@ -7931,6 +8045,14 @@ namespace Tiku.Infrastructure.Persistence.Migrations
.HasColumnType("citext")
.HasColumnName("email");
b.Property<bool>("EmailConfirmed")
.HasColumnType("boolean")
.HasColumnName("email_confirmed");
b.Property<bool>("ForcePasswordChange")
.HasColumnType("boolean")
.HasColumnName("force_password_change");
b.Property<DateTimeOffset?>("LastSeenAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("last_seen_at");
@@ -7940,25 +8062,48 @@ namespace Tiku.Infrastructure.Persistence.Migrations
.HasColumnType("character varying(64)")
.HasColumnName("legacy_id");
b.Property<string>("LegacyPasswordHash")
.HasMaxLength(512)
.HasColumnType("character varying(512)")
.HasColumnName("legacy_password_hash");
b.Property<bool>("LockoutEnabled")
.HasColumnType("boolean")
.HasColumnName("lockout_enabled");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("timestamp with time zone")
.HasColumnName("lockout_end");
b.Property<string>("Name")
.HasMaxLength(200)
.HasColumnType("character varying(200)")
.HasColumnName("name");
b.Property<bool>("PasswordMigrationRequired")
.HasColumnType("boolean")
.HasColumnName("password_migration_required");
b.Property<string>("NormalizedEmail")
.HasMaxLength(320)
.HasColumnType("character varying(320)")
.HasColumnName("normalized_email");
b.Property<string>("NormalizedUserName")
.HasMaxLength(100)
.HasColumnType("character varying(100)")
.HasColumnName("normalized_user_name");
b.Property<string>("PasswordHash")
.HasMaxLength(1024)
.HasColumnType("character varying(1024)")
.HasColumnName("password_hash");
b.Property<string>("Phone")
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("phone");
b.Property<string>("PhoneNumber")
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("phone_number");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("boolean")
.HasColumnName("phone_number_confirmed");
b.Property<string>("PrimaryRole")
.IsRequired()
.HasMaxLength(50)
@@ -7975,36 +8120,50 @@ namespace Tiku.Infrastructure.Persistence.Migrations
.HasColumnType("integer")
.HasColumnName("score");
b.Property<string>("SecurityStamp")
.HasMaxLength(64)
.HasColumnType("character varying(64)")
.HasColumnName("security_stamp");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("status");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("boolean")
.HasColumnName("two_factor_enabled");
b.Property<DateTimeOffset>("UpdatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at")
.HasDefaultValueSql("now()");
b.Property<string>("Username")
b.Property<string>("UserName")
.HasMaxLength(100)
.HasColumnType("character varying(100)")
.HasColumnName("username");
.HasColumnName("user_name");
b.HasKey("Id")
.HasName("pk_users");
b.HasIndex("Email")
.IsUnique()
.HasDatabaseName("ix_users_email");
b.HasIndex("LegacyId")
.IsUnique()
.HasDatabaseName("ix_users_legacy_id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("email_index");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("user_name_index");
b.HasIndex("Phone")
.IsUnique()
.HasDatabaseName("ix_users_phone");
b.HasIndex("Username")
.IsUnique()
.HasDatabaseName("ix_users_username");
b.ToTable("users", (string)null);
});
@@ -8049,12 +8208,6 @@ namespace Tiku.Infrastructure.Persistence.Migrations
.HasColumnType("character varying(255)")
.HasColumnName("provider_subject");
b.Property<JsonElement>("SecretPayload")
.ValueGeneratedOnAdd()
.HasColumnType("jsonb")
.HasColumnName("secret_payload")
.HasDefaultValueSql("'{}'::jsonb");
b.Property<string>("UnionId")
.HasMaxLength(255)
.HasColumnType("character varying(255)")
@@ -12648,6 +12801,95 @@ namespace Tiku.Infrastructure.Persistence.Migrations
b.ToTable("question_versions", (string)null);
});
modelBuilder.Entity("Tiku.Domain.Tenancy.AuthChallenge", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("id")
.HasDefaultValueSql("gen_random_uuid()");
b.Property<DateTimeOffset?>("ConsumedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("consumed_at");
b.Property<DateTimeOffset>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasDefaultValueSql("now()");
b.Property<DateTimeOffset>("ExpiresAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("expires_at");
b.Property<string>("IpAddress")
.HasMaxLength(100)
.HasColumnType("character varying(100)")
.HasColumnName("ip_address");
b.Property<string>("Provider")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("provider");
b.Property<string>("Purpose")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("purpose");
b.Property<string>("Realm")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("realm");
b.Property<string>("SecurityStamp")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("character varying(128)")
.HasColumnName("security_stamp");
b.Property<Guid?>("TenantId")
.HasColumnType("uuid")
.HasColumnName("tenant_id");
b.Property<string>("TokenHash")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)")
.HasColumnName("token_hash");
b.Property<string>("UserAgent")
.HasMaxLength(1024)
.HasColumnType("character varying(1024)")
.HasColumnName("user_agent");
b.Property<Guid>("UserId")
.HasColumnType("uuid")
.HasColumnName("user_id");
b.HasKey("Id")
.HasName("pk_auth_challenges");
b.HasIndex("TenantId")
.HasDatabaseName("ix_auth_challenges_tenant_id");
b.HasIndex("TokenHash")
.IsUnique()
.HasDatabaseName("ix_auth_challenges_token_hash");
b.HasIndex("UserId", "Purpose", "ExpiresAt")
.HasDatabaseName("ix_auth_challenges_user_id_purpose_expires_at");
b.ToTable("auth_challenges", null, t =>
{
t.HasCheckConstraint("ck_auth_challenges_realm_tenant", "(realm = 'tenant' and tenant_id is not null) or (realm = 'platform' and tenant_id is null)");
});
});
modelBuilder.Entity("Tiku.Domain.Tenancy.AuthLoginEvent", b =>
{
b.Property<Guid>("Id")
@@ -12752,20 +12994,53 @@ namespace Tiku.Infrastructure.Persistence.Migrations
.HasColumnName("metadata")
.HasDefaultValueSql("'{}'::jsonb");
b.Property<bool>("MfaSatisfied")
.HasColumnType("boolean")
.HasColumnName("mfa_satisfied");
b.Property<Guid?>("ParentSessionId")
.HasColumnType("uuid")
.HasColumnName("parent_session_id");
b.Property<string>("Provider")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)")
.HasColumnName("provider");
b.Property<string>("Realm")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("realm");
b.Property<Guid?>("ReplacedBySessionId")
.HasColumnType("uuid")
.HasColumnName("replaced_by_session_id");
b.Property<DateTimeOffset?>("RevokedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("revoked_at");
b.Property<Guid>("TenantId")
b.Property<string>("RevokedReason")
.HasMaxLength(100)
.HasColumnType("character varying(100)")
.HasColumnName("revoked_reason");
b.Property<string>("SecurityStamp")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("character varying(128)")
.HasColumnName("security_stamp");
b.Property<Guid?>("TenantId")
.HasColumnType("uuid")
.HasColumnName("tenant_id");
b.Property<Guid>("TokenFamilyId")
.HasColumnType("uuid")
.HasColumnName("token_family_id");
b.Property<string>("TokenHash")
.IsRequired()
.HasMaxLength(256)
@@ -12790,8 +13065,8 @@ namespace Tiku.Infrastructure.Persistence.Migrations
b.HasKey("Id")
.HasName("pk_auth_sessions");
b.HasAlternateKey("TenantId", "Id")
.HasName("ak_auth_sessions_tenant_id_id");
b.HasIndex("TenantId")
.HasDatabaseName("ix_auth_sessions_tenant_id");
b.HasIndex("TokenHash")
.IsUnique()
@@ -12801,11 +13076,17 @@ namespace Tiku.Infrastructure.Persistence.Migrations
b.HasIndex("UserId")
.HasDatabaseName("ix_auth_sessions_user_id");
b.HasIndex("TenantId", "UserId", "ExpiresAt")
.HasDatabaseName("ix_auth_sessions_tenant_id_user_id_expires_at")
b.HasIndex("TokenFamilyId", "RevokedAt")
.HasDatabaseName("ix_auth_sessions_token_family_id_revoked_at");
b.HasIndex("Realm", "TenantId", "UserId", "ExpiresAt")
.HasDatabaseName("ix_auth_sessions_realm_tenant_id_user_id_expires_at")
.HasFilter("revoked_at is null");
b.ToTable("auth_sessions", (string)null);
b.ToTable("auth_sessions", null, t =>
{
t.HasCheckConstraint("ck_auth_sessions_realm_tenant", "(realm = 'tenant' and tenant_id is not null) or (realm = 'platform' and tenant_id is null)");
});
});
modelBuilder.Entity("Tiku.Domain.Tenancy.SmsSendRateLimit", b =>
@@ -12931,9 +13212,6 @@ namespace Tiku.Infrastructure.Persistence.Migrations
b.HasKey("Id")
.HasName("pk_sms_verification_codes");
b.HasAlternateKey("TenantId", "Id")
.HasName("ak_sms_verification_codes_tenant_id_id");
b.HasIndex("TenantId", "Phone", "Purpose")
.IsUnique()
.HasDatabaseName("ix_sms_verification_codes_tenant_id_phone_purpose")
@@ -13623,22 +13901,12 @@ namespace Tiku.Infrastructure.Persistence.Migrations
.HasColumnType("character varying(50)")
.HasColumnName("legacy_role");
b.Property<JsonElement>("Permissions")
.ValueGeneratedOnAdd()
.HasColumnType("jsonb")
.HasColumnName("permissions")
.HasDefaultValueSql("'{}'::jsonb");
b.Property<string>("Role")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("role");
b.Property<Guid?>("RoleTemplateId")
.HasColumnType("uuid")
.HasColumnName("role_template_id");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(32)
@@ -13668,9 +13936,6 @@ namespace Tiku.Infrastructure.Persistence.Migrations
b.HasIndex("UserId")
.HasDatabaseName("ix_tenant_memberships_user_id");
b.HasIndex("TenantId", "RoleTemplateId")
.HasDatabaseName("ix_tenant_memberships_tenant_id_role_template_id");
b.HasIndex("TenantId", "UserId", "Role")
.IsUnique()
.HasDatabaseName("ix_tenant_memberships_tenant_id_user_id_role");
@@ -13678,126 +13943,6 @@ namespace Tiku.Infrastructure.Persistence.Migrations
b.ToTable("tenant_memberships", (string)null);
});
modelBuilder.Entity("Tiku.Domain.Tenancy.TenantRoleTemplate", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("id")
.HasDefaultValueSql("gen_random_uuid()");
b.Property<string>("BaseRole")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("base_role");
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)")
.HasColumnName("code");
b.Property<DateTimeOffset>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasDefaultValueSql("now()");
b.Property<Guid?>("CreatedBy")
.HasColumnType("uuid")
.HasColumnName("created_by");
b.Property<JsonElement>("DataScope")
.ValueGeneratedOnAdd()
.HasColumnType("jsonb")
.HasColumnName("data_scope")
.HasDefaultValueSql("'{}'::jsonb");
b.Property<string>("Description")
.HasColumnType("text")
.HasColumnName("description");
b.Property<JsonElement>("FieldPermissions")
.ValueGeneratedOnAdd()
.HasColumnType("jsonb")
.HasColumnName("field_permissions")
.HasDefaultValueSql("'{}'::jsonb");
b.Property<bool>("IsSystem")
.HasColumnType("boolean")
.HasColumnName("is_system");
b.Property<JsonElement>("MenuPermissions")
.ValueGeneratedOnAdd()
.HasColumnType("jsonb")
.HasColumnName("menu_permissions")
.HasDefaultValueSql("'{}'::jsonb");
b.Property<JsonElement>("ModulePermissions")
.ValueGeneratedOnAdd()
.HasColumnType("jsonb")
.HasColumnName("module_permissions")
.HasDefaultValueSql("'{}'::jsonb");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)")
.HasColumnName("name");
b.Property<JsonElement>("Permissions")
.ValueGeneratedOnAdd()
.HasColumnType("jsonb")
.HasColumnName("permissions")
.HasDefaultValueSql("'{}'::jsonb");
b.Property<int>("SortOrder")
.HasColumnType("integer")
.HasColumnName("sort_order");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("status");
b.Property<Guid>("TenantId")
.HasColumnType("uuid")
.HasColumnName("tenant_id");
b.Property<DateTimeOffset>("UpdatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at")
.HasDefaultValueSql("now()");
b.Property<Guid?>("UpdatedBy")
.HasColumnType("uuid")
.HasColumnName("updated_by");
b.HasKey("Id")
.HasName("pk_tenant_role_templates");
b.HasAlternateKey("TenantId", "Id")
.HasName("ak_tenant_role_templates_tenant_id_id");
b.HasIndex("CreatedBy")
.HasDatabaseName("ix_tenant_role_templates_created_by");
b.HasIndex("UpdatedBy")
.HasDatabaseName("ix_tenant_role_templates_updated_by");
b.HasIndex("TenantId", "Code")
.IsUnique()
.HasDatabaseName("ix_tenant_role_templates_tenant_id_code");
b.HasIndex("TenantId", "Status", "SortOrder")
.HasDatabaseName("ix_tenant_role_templates_tenant_id_status_sort_order");
b.ToTable("tenant_role_templates", (string)null);
});
modelBuilder.Entity("Tiku.Domain.Tenancy.TenantSecret", b =>
{
b.Property<Guid>("Id")
@@ -14153,6 +14298,36 @@ namespace Tiku.Infrastructure.Persistence.Migrations
b.ToTable("tenant_student_notes", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
{
b.HasOne("Tiku.Domain.Identity.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_user_claims_users_user_id");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
{
b.HasOne("Tiku.Domain.Identity.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_user_logins_users_user_id");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
{
b.HasOne("Tiku.Domain.Identity.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_user_tokens_users_user_id");
});
modelBuilder.Entity("Tiku.Domain.Catalog.Category", b =>
{
b.HasOne("Tiku.Domain.Tenancy.Tenant", null)
@@ -17204,12 +17379,28 @@ namespace Tiku.Infrastructure.Persistence.Migrations
.HasConstraintName("fk_question_versions_questions_tenant_id_question_id");
});
modelBuilder.Entity("Tiku.Domain.Tenancy.AuthLoginEvent", b =>
modelBuilder.Entity("Tiku.Domain.Tenancy.AuthChallenge", b =>
{
b.HasOne("Tiku.Domain.Tenancy.Tenant", null)
.WithMany()
.HasForeignKey("TenantId")
.OnDelete(DeleteBehavior.Cascade)
.HasConstraintName("fk_auth_challenges_tenants_tenant_id");
b.HasOne("Tiku.Domain.Identity.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_auth_challenges_users_user_id");
});
modelBuilder.Entity("Tiku.Domain.Tenancy.AuthLoginEvent", b =>
{
b.HasOne("Tiku.Domain.Tenancy.Tenant", null)
.WithMany()
.HasForeignKey("TenantId")
.OnDelete(DeleteBehavior.SetNull)
.IsRequired()
.HasConstraintName("fk_auth_login_events_tenants_tenant_id");
@@ -17226,7 +17417,6 @@ namespace Tiku.Infrastructure.Persistence.Migrations
.WithMany()
.HasForeignKey("TenantId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_auth_sessions_tenants_tenant_id");
b.HasOne("Tiku.Domain.Identity.User", null)
@@ -17375,35 +17565,6 @@ namespace Tiku.Infrastructure.Persistence.Migrations
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_tenant_memberships_users_user_id");
b.HasOne("Tiku.Domain.Tenancy.TenantRoleTemplate", null)
.WithMany()
.HasForeignKey("TenantId", "RoleTemplateId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Restrict)
.HasConstraintName("fk_tenant_memberships_tenant_role_templates_tenant_id_role_tem~");
});
modelBuilder.Entity("Tiku.Domain.Tenancy.TenantRoleTemplate", b =>
{
b.HasOne("Tiku.Domain.Identity.User", null)
.WithMany()
.HasForeignKey("CreatedBy")
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("fk_tenant_role_templates_users_created_by");
b.HasOne("Tiku.Domain.Tenancy.Tenant", null)
.WithMany()
.HasForeignKey("TenantId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_tenant_role_templates_tenants_tenant_id");
b.HasOne("Tiku.Domain.Identity.User", null)
.WithMany()
.HasForeignKey("UpdatedBy")
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("fk_tenant_role_templates_users_updated_by");
});
modelBuilder.Entity("Tiku.Domain.Tenancy.TenantSecret", b =>