feat: add phase five operations foundation

This commit is contained in:
2026-07-28 09:57:50 +08:00
parent 71793a2de0
commit f22f329d33
39 changed files with 4810 additions and 160 deletions

View File

@@ -1,6 +1,7 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Tiku.Domain.Catalog;
using Tiku.Domain.Content;
using Tiku.Domain.Identity;
using Tiku.Domain.Operations;
using Tiku.Domain.QuestionBanks;
@@ -79,6 +80,206 @@ internal sealed class AuditLogConfiguration : IEntityTypeConfiguration<AuditLog>
}
}
internal sealed class BackendPermissionConfiguration : IEntityTypeConfiguration<BackendPermission>
{
public void Configure(EntityTypeBuilder<BackendPermission> builder)
{
builder.ConfigureEntity("backend_permissions");
builder.ConfigureTimestamps();
builder.Property(entity => entity.Code).HasMaxLength(160);
builder.Property(entity => entity.Name).HasMaxLength(200);
builder.Property(entity => entity.Area).HasSnakeCaseEnum();
builder.Property(entity => entity.Module).HasMaxLength(100);
builder.Property(entity => entity.Description).HasMaxLength(1000);
builder.HasIndex(entity => entity.Code).IsUnique();
builder.HasIndex(entity => new { entity.Area, entity.Module, entity.SortOrder });
}
}
internal sealed class BackendMenuConfiguration : IEntityTypeConfiguration<BackendMenu>
{
public void Configure(EntityTypeBuilder<BackendMenu> builder)
{
builder.ConfigureEntity("backend_menus");
builder.ConfigureTimestamps();
builder.Property(entity => entity.Code).HasMaxLength(160);
builder.Property(entity => entity.ParentCode).HasMaxLength(160);
builder.Property(entity => entity.Title).HasMaxLength(200);
builder.Property(entity => entity.Area).HasSnakeCaseEnum();
builder.Property(entity => entity.Path).HasMaxLength(1024);
builder.Property(entity => entity.Icon).HasMaxLength(100);
builder.Property(entity => entity.PermissionCode).HasMaxLength(160);
builder.HasIndex(entity => entity.Code).IsUnique();
builder.HasIndex(entity => new { entity.Area, entity.ParentCode, entity.SortOrder });
builder.HasOne<BackendPermission>().WithMany()
.HasPrincipalKey(entity => entity.Code)
.HasForeignKey(entity => entity.PermissionCode)
.OnDelete(DeleteBehavior.SetNull);
}
}
internal sealed class TenantBackendRoleConfiguration : IEntityTypeConfiguration<TenantBackendRole>
{
public void Configure(EntityTypeBuilder<TenantBackendRole> builder)
{
builder.ConfigureTenantEntity("tenant_backend_roles");
builder.ConfigureTimestamps();
builder.Property(entity => entity.Code).HasMaxLength(100);
builder.Property(entity => entity.Name).HasMaxLength(200);
builder.Property(entity => entity.Status).HasSnakeCaseEnum();
builder.Property(entity => entity.Description).HasMaxLength(1000);
builder.Property(entity => entity.DataScope).IsJson("{}");
builder.HasIndex(entity => new { entity.TenantId, entity.Code }).IsUnique();
builder.HasIndex(entity => new { entity.TenantId, entity.Status, entity.Code });
}
}
internal sealed class TenantBackendRolePermissionConfiguration : IEntityTypeConfiguration<TenantBackendRolePermission>
{
public void Configure(EntityTypeBuilder<TenantBackendRolePermission> builder)
{
builder.ConfigureEntity("tenant_backend_role_permissions");
builder.Property(entity => entity.PermissionCode).HasMaxLength(160);
builder.Property(entity => entity.CreatedAt).HasDefaultValueSql("now()");
builder.HasAlternateKey(entity => new { entity.TenantId, entity.Id });
builder.HasIndex(entity => new { entity.TenantId, entity.RoleId, entity.PermissionCode }).IsUnique();
builder.HasOne<TenantBackendRole>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.RoleId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne<BackendPermission>().WithMany()
.HasPrincipalKey(entity => entity.Code)
.HasForeignKey(entity => entity.PermissionCode)
.OnDelete(DeleteBehavior.Restrict);
}
}
internal sealed class TenantBackendRoleMenuConfiguration : IEntityTypeConfiguration<TenantBackendRoleMenu>
{
public void Configure(EntityTypeBuilder<TenantBackendRoleMenu> builder)
{
builder.ConfigureEntity("tenant_backend_role_menus");
builder.Property(entity => entity.MenuCode).HasMaxLength(160);
builder.Property(entity => entity.CreatedAt).HasDefaultValueSql("now()");
builder.HasAlternateKey(entity => new { entity.TenantId, entity.Id });
builder.HasIndex(entity => new { entity.TenantId, entity.RoleId, entity.MenuCode }).IsUnique();
builder.HasOne<TenantBackendRole>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.RoleId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne<BackendMenu>().WithMany()
.HasPrincipalKey(entity => entity.Code)
.HasForeignKey(entity => entity.MenuCode)
.OnDelete(DeleteBehavior.Restrict);
}
}
internal sealed class TenantBackendUserRoleConfiguration : IEntityTypeConfiguration<TenantBackendUserRole>
{
public void Configure(EntityTypeBuilder<TenantBackendUserRole> builder)
{
builder.ConfigureEntity("tenant_backend_user_roles");
builder.Property(entity => entity.CreatedAt).HasDefaultValueSql("now()");
builder.HasAlternateKey(entity => new { entity.TenantId, entity.Id });
builder.HasIndex(entity => new { entity.TenantId, entity.UserId, entity.RoleId }).IsUnique();
builder.HasOne<TenantBackendRole>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.RoleId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne<User>().WithMany()
.HasForeignKey(entity => entity.UserId)
.OnDelete(DeleteBehavior.Cascade);
}
}
internal sealed class PlatformBackendRoleConfiguration : IEntityTypeConfiguration<PlatformBackendRole>
{
public void Configure(EntityTypeBuilder<PlatformBackendRole> builder)
{
builder.ConfigureEntity("platform_backend_roles");
builder.ConfigureTimestamps();
builder.Property(entity => entity.Code).HasMaxLength(100);
builder.Property(entity => entity.Name).HasMaxLength(200);
builder.Property(entity => entity.Status).HasSnakeCaseEnum();
builder.Property(entity => entity.Description).HasMaxLength(1000);
builder.HasIndex(entity => entity.Code).IsUnique();
builder.HasIndex(entity => new { entity.Status, entity.Code });
}
}
internal sealed class PlatformBackendRolePermissionConfiguration : IEntityTypeConfiguration<PlatformBackendRolePermission>
{
public void Configure(EntityTypeBuilder<PlatformBackendRolePermission> builder)
{
builder.ConfigureEntity("platform_backend_role_permissions");
builder.Property(entity => entity.PermissionCode).HasMaxLength(160);
builder.Property(entity => entity.CreatedAt).HasDefaultValueSql("now()");
builder.HasIndex(entity => new { entity.RoleId, entity.PermissionCode }).IsUnique();
builder.HasOne<PlatformBackendRole>().WithMany()
.HasForeignKey(entity => entity.RoleId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne<BackendPermission>().WithMany()
.HasPrincipalKey(entity => entity.Code)
.HasForeignKey(entity => entity.PermissionCode)
.OnDelete(DeleteBehavior.Restrict);
}
}
internal sealed class PlatformBackendRoleMenuConfiguration : IEntityTypeConfiguration<PlatformBackendRoleMenu>
{
public void Configure(EntityTypeBuilder<PlatformBackendRoleMenu> builder)
{
builder.ConfigureEntity("platform_backend_role_menus");
builder.Property(entity => entity.MenuCode).HasMaxLength(160);
builder.Property(entity => entity.CreatedAt).HasDefaultValueSql("now()");
builder.HasIndex(entity => new { entity.RoleId, entity.MenuCode }).IsUnique();
builder.HasOne<PlatformBackendRole>().WithMany()
.HasForeignKey(entity => entity.RoleId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne<BackendMenu>().WithMany()
.HasPrincipalKey(entity => entity.Code)
.HasForeignKey(entity => entity.MenuCode)
.OnDelete(DeleteBehavior.Restrict);
}
}
internal sealed class PlatformBackendUserRoleConfiguration : IEntityTypeConfiguration<PlatformBackendUserRole>
{
public void Configure(EntityTypeBuilder<PlatformBackendUserRole> builder)
{
builder.ConfigureEntity("platform_backend_user_roles");
builder.Property(entity => entity.CreatedAt).HasDefaultValueSql("now()");
builder.HasIndex(entity => new { entity.UserId, entity.RoleId }).IsUnique();
builder.HasOne<PlatformBackendRole>().WithMany()
.HasForeignKey(entity => entity.RoleId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasOne<User>().WithMany()
.HasForeignKey(entity => entity.UserId)
.OnDelete(DeleteBehavior.Cascade);
}
}
internal sealed class BackgroundJobConfiguration : IEntityTypeConfiguration<BackgroundJob>
{
public void Configure(EntityTypeBuilder<BackgroundJob> builder)
{
builder.ConfigureTenantEntity("background_jobs");
builder.ConfigureTimestamps();
builder.Property(entity => entity.JobType).HasMaxLength(100);
builder.Property(entity => entity.Status).HasSnakeCaseEnum();
builder.Property(entity => entity.Payload).IsJson("{}");
builder.Property(entity => entity.LockedBy).HasMaxLength(200);
builder.Property(entity => entity.LastError).HasMaxLength(4000);
builder.Property(entity => entity.Result).IsJson("{}");
builder.HasIndex(entity => new { entity.TenantId, entity.Status, entity.RunAfter, entity.CreatedAt });
builder.HasIndex(entity => new { entity.TenantId, entity.JobType, entity.Status, entity.CreatedAt });
builder.HasOne<ContentAsset>().WithMany()
.HasForeignKey(entity => new { entity.TenantId, entity.OutputAssetId })
.HasPrincipalKey(entity => new { entity.TenantId, entity.Id })
.OnDelete(DeleteBehavior.SetNull);
}
}
internal sealed class UserNotificationConfiguration : IEntityTypeConfiguration<UserNotification>
{
public void Configure(EntityTypeBuilder<UserNotification> builder)

View File

@@ -13,7 +13,7 @@ using Tiku.Infrastructure.Persistence;
namespace Tiku.Infrastructure.Persistence.Migrations
{
[DbContext(typeof(TikuDbContext))]
[Migration("20260727093301_InitialSchema")]
[Migration("20260728014412_InitialSchema")]
partial class InitialSchema
{
/// <inheritdoc />
@@ -9879,6 +9879,268 @@ namespace Tiku.Infrastructure.Persistence.Migrations
b.ToTable("audit_logs", (string)null);
});
modelBuilder.Entity("Tiku.Domain.Operations.BackendMenu", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("id")
.HasDefaultValueSql("gen_random_uuid()");
b.Property<string>("Area")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("area");
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(160)
.HasColumnType("character varying(160)")
.HasColumnName("code");
b.Property<DateTimeOffset>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasDefaultValueSql("now()");
b.Property<string>("Icon")
.HasMaxLength(100)
.HasColumnType("character varying(100)")
.HasColumnName("icon");
b.Property<bool>("IsActive")
.HasColumnType("boolean")
.HasColumnName("is_active");
b.Property<string>("ParentCode")
.HasMaxLength(160)
.HasColumnType("character varying(160)")
.HasColumnName("parent_code");
b.Property<string>("Path")
.HasMaxLength(1024)
.HasColumnType("character varying(1024)")
.HasColumnName("path");
b.Property<string>("PermissionCode")
.HasMaxLength(160)
.HasColumnType("character varying(160)")
.HasColumnName("permission_code");
b.Property<int>("SortOrder")
.HasColumnType("integer")
.HasColumnName("sort_order");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)")
.HasColumnName("title");
b.Property<DateTimeOffset>("UpdatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at")
.HasDefaultValueSql("now()");
b.HasKey("Id")
.HasName("pk_backend_menus");
b.HasAlternateKey("Code")
.HasName("ak_backend_menus_code");
b.HasIndex("Code")
.IsUnique()
.HasDatabaseName("ix_backend_menus_code");
b.HasIndex("PermissionCode")
.HasDatabaseName("ix_backend_menus_permission_code");
b.HasIndex("Area", "ParentCode", "SortOrder")
.HasDatabaseName("ix_backend_menus_area_parent_code_sort_order");
b.ToTable("backend_menus", (string)null);
});
modelBuilder.Entity("Tiku.Domain.Operations.BackendPermission", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("id")
.HasDefaultValueSql("gen_random_uuid()");
b.Property<string>("Area")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("area");
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(160)
.HasColumnType("character varying(160)")
.HasColumnName("code");
b.Property<DateTimeOffset>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasDefaultValueSql("now()");
b.Property<string>("Description")
.HasMaxLength(1000)
.HasColumnType("character varying(1000)")
.HasColumnName("description");
b.Property<bool>("IsSystem")
.HasColumnType("boolean")
.HasColumnName("is_system");
b.Property<string>("Module")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)")
.HasColumnName("module");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)")
.HasColumnName("name");
b.Property<int>("SortOrder")
.HasColumnType("integer")
.HasColumnName("sort_order");
b.Property<DateTimeOffset>("UpdatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at")
.HasDefaultValueSql("now()");
b.HasKey("Id")
.HasName("pk_backend_permissions");
b.HasAlternateKey("Code")
.HasName("ak_backend_permissions_code");
b.HasIndex("Code")
.IsUnique()
.HasDatabaseName("ix_backend_permissions_code");
b.HasIndex("Area", "Module", "SortOrder")
.HasDatabaseName("ix_backend_permissions_area_module_sort_order");
b.ToTable("backend_permissions", (string)null);
});
modelBuilder.Entity("Tiku.Domain.Operations.BackgroundJob", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("id")
.HasDefaultValueSql("gen_random_uuid()");
b.Property<DateTimeOffset?>("CompletedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("completed_at");
b.Property<DateTimeOffset>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasDefaultValueSql("now()");
b.Property<string>("JobType")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)")
.HasColumnName("job_type");
b.Property<string>("LastError")
.HasMaxLength(4000)
.HasColumnType("character varying(4000)")
.HasColumnName("last_error");
b.Property<DateTimeOffset?>("LockExpiresAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("lock_expires_at");
b.Property<string>("LockedBy")
.HasMaxLength(200)
.HasColumnType("character varying(200)")
.HasColumnName("locked_by");
b.Property<int>("MaxRetries")
.HasColumnType("integer")
.HasColumnName("max_retries");
b.Property<Guid?>("OutputAssetId")
.HasColumnType("uuid")
.HasColumnName("output_asset_id");
b.Property<JsonElement>("Payload")
.ValueGeneratedOnAdd()
.HasColumnType("jsonb")
.HasColumnName("payload")
.HasDefaultValueSql("'{}'::jsonb");
b.Property<JsonElement>("Result")
.ValueGeneratedOnAdd()
.HasColumnType("jsonb")
.HasColumnName("result")
.HasDefaultValueSql("'{}'::jsonb");
b.Property<int>("RetryCount")
.HasColumnType("integer")
.HasColumnName("retry_count");
b.Property<DateTimeOffset?>("RunAfter")
.HasColumnType("timestamp with time zone")
.HasColumnName("run_after");
b.Property<DateTimeOffset?>("StartedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("started_at");
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.HasKey("Id")
.HasName("pk_background_jobs");
b.HasAlternateKey("TenantId", "Id")
.HasName("ak_background_jobs_tenant_id_id");
b.HasIndex("TenantId", "OutputAssetId")
.HasDatabaseName("ix_background_jobs_tenant_id_output_asset_id");
b.HasIndex("TenantId", "JobType", "Status", "CreatedAt")
.HasDatabaseName("ix_background_jobs_tenant_id_job_type_status_created_at");
b.HasIndex("TenantId", "Status", "RunAfter", "CreatedAt")
.HasDatabaseName("ix_background_jobs_tenant_id_status_run_after_created_at");
b.ToTable("background_jobs", (string)null);
});
modelBuilder.Entity("Tiku.Domain.Operations.Badge", b =>
{
b.Property<Guid>("Id")
@@ -10138,6 +10400,381 @@ namespace Tiku.Infrastructure.Persistence.Migrations
b.ToTable("faqs", (string)null);
});
modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendRole", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("id")
.HasDefaultValueSql("gen_random_uuid()");
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<string>("Description")
.HasMaxLength(1000)
.HasColumnType("character varying(1000)")
.HasColumnName("description");
b.Property<bool>("IsSystem")
.HasColumnType("boolean")
.HasColumnName("is_system");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)")
.HasColumnName("name");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("status");
b.Property<DateTimeOffset>("UpdatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at")
.HasDefaultValueSql("now()");
b.HasKey("Id")
.HasName("pk_platform_backend_roles");
b.HasIndex("Code")
.IsUnique()
.HasDatabaseName("ix_platform_backend_roles_code");
b.HasIndex("Status", "Code")
.HasDatabaseName("ix_platform_backend_roles_status_code");
b.ToTable("platform_backend_roles", (string)null);
});
modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendRoleMenu", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("id")
.HasDefaultValueSql("gen_random_uuid()");
b.Property<DateTimeOffset>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasDefaultValueSql("now()");
b.Property<string>("MenuCode")
.IsRequired()
.HasMaxLength(160)
.HasColumnType("character varying(160)")
.HasColumnName("menu_code");
b.Property<Guid>("RoleId")
.HasColumnType("uuid")
.HasColumnName("role_id");
b.HasKey("Id")
.HasName("pk_platform_backend_role_menus");
b.HasIndex("MenuCode")
.HasDatabaseName("ix_platform_backend_role_menus_menu_code");
b.HasIndex("RoleId", "MenuCode")
.IsUnique()
.HasDatabaseName("ix_platform_backend_role_menus_role_id_menu_code");
b.ToTable("platform_backend_role_menus", (string)null);
});
modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendRolePermission", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("id")
.HasDefaultValueSql("gen_random_uuid()");
b.Property<DateTimeOffset>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasDefaultValueSql("now()");
b.Property<string>("PermissionCode")
.IsRequired()
.HasMaxLength(160)
.HasColumnType("character varying(160)")
.HasColumnName("permission_code");
b.Property<Guid>("RoleId")
.HasColumnType("uuid")
.HasColumnName("role_id");
b.HasKey("Id")
.HasName("pk_platform_backend_role_permissions");
b.HasIndex("PermissionCode")
.HasDatabaseName("ix_platform_backend_role_permissions_permission_code");
b.HasIndex("RoleId", "PermissionCode")
.IsUnique()
.HasDatabaseName("ix_platform_backend_role_permissions_role_id_permission_code");
b.ToTable("platform_backend_role_permissions", (string)null);
});
modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendUserRole", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("id")
.HasDefaultValueSql("gen_random_uuid()");
b.Property<DateTimeOffset>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasDefaultValueSql("now()");
b.Property<Guid>("RoleId")
.HasColumnType("uuid")
.HasColumnName("role_id");
b.Property<Guid>("UserId")
.HasColumnType("uuid")
.HasColumnName("user_id");
b.HasKey("Id")
.HasName("pk_platform_backend_user_roles");
b.HasIndex("RoleId")
.HasDatabaseName("ix_platform_backend_user_roles_role_id");
b.HasIndex("UserId", "RoleId")
.IsUnique()
.HasDatabaseName("ix_platform_backend_user_roles_user_id_role_id");
b.ToTable("platform_backend_user_roles", (string)null);
});
modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRole", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("id")
.HasDefaultValueSql("gen_random_uuid()");
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<JsonElement>("DataScope")
.ValueGeneratedOnAdd()
.HasColumnType("jsonb")
.HasColumnName("data_scope")
.HasDefaultValueSql("'{}'::jsonb");
b.Property<string>("Description")
.HasMaxLength(1000)
.HasColumnType("character varying(1000)")
.HasColumnName("description");
b.Property<bool>("IsSystem")
.HasColumnType("boolean")
.HasColumnName("is_system");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)")
.HasColumnName("name");
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.HasKey("Id")
.HasName("pk_tenant_backend_roles");
b.HasAlternateKey("TenantId", "Id")
.HasName("ak_tenant_backend_roles_tenant_id_id");
b.HasIndex("TenantId", "Code")
.IsUnique()
.HasDatabaseName("ix_tenant_backend_roles_tenant_id_code");
b.HasIndex("TenantId", "Status", "Code")
.HasDatabaseName("ix_tenant_backend_roles_tenant_id_status_code");
b.ToTable("tenant_backend_roles", (string)null);
});
modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRoleMenu", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("id")
.HasDefaultValueSql("gen_random_uuid()");
b.Property<DateTimeOffset>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasDefaultValueSql("now()");
b.Property<string>("MenuCode")
.IsRequired()
.HasMaxLength(160)
.HasColumnType("character varying(160)")
.HasColumnName("menu_code");
b.Property<Guid>("RoleId")
.HasColumnType("uuid")
.HasColumnName("role_id");
b.Property<Guid>("TenantId")
.HasColumnType("uuid")
.HasColumnName("tenant_id");
b.HasKey("Id")
.HasName("pk_tenant_backend_role_menus");
b.HasAlternateKey("TenantId", "Id")
.HasName("ak_tenant_backend_role_menus_tenant_id_id");
b.HasIndex("MenuCode")
.HasDatabaseName("ix_tenant_backend_role_menus_menu_code");
b.HasIndex("TenantId", "RoleId", "MenuCode")
.IsUnique()
.HasDatabaseName("ix_tenant_backend_role_menus_tenant_id_role_id_menu_code");
b.ToTable("tenant_backend_role_menus", (string)null);
});
modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRolePermission", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("id")
.HasDefaultValueSql("gen_random_uuid()");
b.Property<DateTimeOffset>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasDefaultValueSql("now()");
b.Property<string>("PermissionCode")
.IsRequired()
.HasMaxLength(160)
.HasColumnType("character varying(160)")
.HasColumnName("permission_code");
b.Property<Guid>("RoleId")
.HasColumnType("uuid")
.HasColumnName("role_id");
b.Property<Guid>("TenantId")
.HasColumnType("uuid")
.HasColumnName("tenant_id");
b.HasKey("Id")
.HasName("pk_tenant_backend_role_permissions");
b.HasAlternateKey("TenantId", "Id")
.HasName("ak_tenant_backend_role_permissions_tenant_id_id");
b.HasIndex("PermissionCode")
.HasDatabaseName("ix_tenant_backend_role_permissions_permission_code");
b.HasIndex("TenantId", "RoleId", "PermissionCode")
.IsUnique()
.HasDatabaseName("ix_tenant_backend_role_permissions_tenant_id_role_id_permissio~");
b.ToTable("tenant_backend_role_permissions", (string)null);
});
modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendUserRole", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("id")
.HasDefaultValueSql("gen_random_uuid()");
b.Property<DateTimeOffset>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasDefaultValueSql("now()");
b.Property<Guid>("RoleId")
.HasColumnType("uuid")
.HasColumnName("role_id");
b.Property<Guid>("TenantId")
.HasColumnType("uuid")
.HasColumnName("tenant_id");
b.Property<Guid>("UserId")
.HasColumnType("uuid")
.HasColumnName("user_id");
b.HasKey("Id")
.HasName("pk_tenant_backend_user_roles");
b.HasAlternateKey("TenantId", "Id")
.HasName("ak_tenant_backend_user_roles_tenant_id_id");
b.HasIndex("UserId")
.HasDatabaseName("ix_tenant_backend_user_roles_user_id");
b.HasIndex("TenantId", "RoleId")
.HasDatabaseName("ix_tenant_backend_user_roles_tenant_id_role_id");
b.HasIndex("TenantId", "UserId", "RoleId")
.IsUnique()
.HasDatabaseName("ix_tenant_backend_user_roles_tenant_id_user_id_role_id");
b.ToTable("tenant_backend_user_roles", (string)null);
});
modelBuilder.Entity("Tiku.Domain.Operations.TenantContentNotification", b =>
{
b.Property<Guid>("Id")
@@ -15994,6 +16631,33 @@ namespace Tiku.Infrastructure.Persistence.Migrations
.HasConstraintName("fk_audit_logs_tenants_tenant_id");
});
modelBuilder.Entity("Tiku.Domain.Operations.BackendMenu", b =>
{
b.HasOne("Tiku.Domain.Operations.BackendPermission", null)
.WithMany()
.HasForeignKey("PermissionCode")
.HasPrincipalKey("Code")
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("fk_backend_menus_backend_permissions_permission_code");
});
modelBuilder.Entity("Tiku.Domain.Operations.BackgroundJob", b =>
{
b.HasOne("Tiku.Domain.Tenancy.Tenant", null)
.WithMany()
.HasForeignKey("TenantId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_background_jobs_tenants_tenant_id");
b.HasOne("Tiku.Domain.Content.ContentAsset", null)
.WithMany()
.HasForeignKey("TenantId", "OutputAssetId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("fk_background_jobs_content_assets_tenant_id_output_asset_id");
});
modelBuilder.Entity("Tiku.Domain.Operations.Badge", b =>
{
b.HasOne("Tiku.Domain.Tenancy.Tenant", null)
@@ -16038,6 +16702,125 @@ namespace Tiku.Infrastructure.Persistence.Migrations
.HasConstraintName("fk_faqs_regions_tenant_id_region_id");
});
modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendRoleMenu", b =>
{
b.HasOne("Tiku.Domain.Operations.BackendMenu", null)
.WithMany()
.HasForeignKey("MenuCode")
.HasPrincipalKey("Code")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired()
.HasConstraintName("fk_platform_backend_role_menus_backend_menus_menu_code");
b.HasOne("Tiku.Domain.Operations.PlatformBackendRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_platform_backend_role_menus_platform_backend_roles_role_id");
});
modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendRolePermission", b =>
{
b.HasOne("Tiku.Domain.Operations.BackendPermission", null)
.WithMany()
.HasForeignKey("PermissionCode")
.HasPrincipalKey("Code")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired()
.HasConstraintName("fk_platform_backend_role_permissions_backend_permissions_permi~");
b.HasOne("Tiku.Domain.Operations.PlatformBackendRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_platform_backend_role_permissions_platform_backend_roles_ro~");
});
modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendUserRole", b =>
{
b.HasOne("Tiku.Domain.Operations.PlatformBackendRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_platform_backend_user_roles_platform_backend_roles_role_id");
b.HasOne("Tiku.Domain.Identity.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_platform_backend_user_roles_users_user_id");
});
modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRole", b =>
{
b.HasOne("Tiku.Domain.Tenancy.Tenant", null)
.WithMany()
.HasForeignKey("TenantId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_tenant_backend_roles_tenants_tenant_id");
});
modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRoleMenu", b =>
{
b.HasOne("Tiku.Domain.Operations.BackendMenu", null)
.WithMany()
.HasForeignKey("MenuCode")
.HasPrincipalKey("Code")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired()
.HasConstraintName("fk_tenant_backend_role_menus_backend_menus_menu_code");
b.HasOne("Tiku.Domain.Operations.TenantBackendRole", null)
.WithMany()
.HasForeignKey("TenantId", "RoleId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_tenant_backend_role_menus_tenant_backend_roles_tenant_id_ro~");
});
modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRolePermission", b =>
{
b.HasOne("Tiku.Domain.Operations.BackendPermission", null)
.WithMany()
.HasForeignKey("PermissionCode")
.HasPrincipalKey("Code")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired()
.HasConstraintName("fk_tenant_backend_role_permissions_backend_permissions_permiss~");
b.HasOne("Tiku.Domain.Operations.TenantBackendRole", null)
.WithMany()
.HasForeignKey("TenantId", "RoleId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_tenant_backend_role_permissions_tenant_backend_roles_tenant~");
});
modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendUserRole", b =>
{
b.HasOne("Tiku.Domain.Identity.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_tenant_backend_user_roles_users_user_id");
b.HasOne("Tiku.Domain.Operations.TenantBackendRole", null)
.WithMany()
.HasForeignKey("TenantId", "RoleId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_tenant_backend_user_roles_tenant_backend_roles_tenant_id_ro~");
});
modelBuilder.Entity("Tiku.Domain.Operations.TenantContentNotification", b =>
{
b.HasOne("Tiku.Domain.Identity.User", null)

View File

@@ -1,6 +1,7 @@
using System;
using System.Text.Json;
using Microsoft.EntityFrameworkCore.Migrations;
using Tiku.Infrastructure.Persistence;
#nullable disable
@@ -16,6 +17,45 @@ namespace Tiku.Infrastructure.Persistence.Migrations
.Annotation("Npgsql:PostgresExtension:citext", ",,")
.Annotation("Npgsql:PostgresExtension:ltree", ",,");
migrationBuilder.CreateTable(
name: "backend_permissions",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"),
code = table.Column<string>(type: "character varying(160)", maxLength: 160, nullable: false),
name = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
area = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
module = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
description = table.Column<string>(type: "character varying(1000)", maxLength: 1000, nullable: true),
is_system = table.Column<bool>(type: "boolean", nullable: false),
sort_order = table.Column<int>(type: "integer", 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_backend_permissions", x => x.id);
table.UniqueConstraint("ak_backend_permissions_code", x => x.code);
});
migrationBuilder.CreateTable(
name: "platform_backend_roles",
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),
status = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
is_system = table.Column<bool>(type: "boolean", nullable: false),
description = table.Column<string>(type: "character varying(1000)", maxLength: 1000, nullable: true),
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_platform_backend_roles", x => x.id);
});
migrationBuilder.CreateTable(
name: "platform_dunning_notification_channels",
columns: table => new
@@ -116,6 +156,87 @@ namespace Tiku.Infrastructure.Persistence.Migrations
table.PrimaryKey("pk_users", x => x.id);
});
migrationBuilder.CreateTable(
name: "backend_menus",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"),
code = table.Column<string>(type: "character varying(160)", maxLength: 160, nullable: false),
parent_code = table.Column<string>(type: "character varying(160)", maxLength: 160, nullable: true),
title = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
area = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
path = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
icon = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: true),
permission_code = table.Column<string>(type: "character varying(160)", maxLength: 160, nullable: true),
sort_order = table.Column<int>(type: "integer", nullable: false),
is_active = table.Column<bool>(type: "boolean", 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_backend_menus", x => x.id);
table.UniqueConstraint("ak_backend_menus_code", x => x.code);
table.ForeignKey(
name: "fk_backend_menus_backend_permissions_permission_code",
column: x => x.permission_code,
principalTable: "backend_permissions",
principalColumn: "code",
onDelete: ReferentialAction.SetNull);
});
migrationBuilder.CreateTable(
name: "platform_backend_role_permissions",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"),
role_id = table.Column<Guid>(type: "uuid", nullable: false),
permission_code = table.Column<string>(type: "character varying(160)", maxLength: 160, nullable: false),
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()")
},
constraints: table =>
{
table.PrimaryKey("pk_platform_backend_role_permissions", x => x.id);
table.ForeignKey(
name: "fk_platform_backend_role_permissions_backend_permissions_permi~",
column: x => x.permission_code,
principalTable: "backend_permissions",
principalColumn: "code",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "fk_platform_backend_role_permissions_platform_backend_roles_ro~",
column: x => x.role_id,
principalTable: "platform_backend_roles",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "platform_backend_user_roles",
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_id = table.Column<Guid>(type: "uuid", nullable: false),
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()")
},
constraints: table =>
{
table.PrimaryKey("pk_platform_backend_user_roles", x => x.id);
table.ForeignKey(
name: "fk_platform_backend_user_roles_platform_backend_roles_role_id",
column: x => x.role_id,
principalTable: "platform_backend_roles",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_platform_backend_user_roles_users_user_id",
column: x => x.user_id,
principalTable: "users",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "tenants",
columns: table => new
@@ -171,6 +292,32 @@ namespace Tiku.Infrastructure.Persistence.Migrations
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "platform_backend_role_menus",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"),
role_id = table.Column<Guid>(type: "uuid", nullable: false),
menu_code = table.Column<string>(type: "character varying(160)", maxLength: 160, nullable: false),
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()")
},
constraints: table =>
{
table.PrimaryKey("pk_platform_backend_role_menus", x => x.id);
table.ForeignKey(
name: "fk_platform_backend_role_menus_backend_menus_menu_code",
column: x => x.menu_code,
principalTable: "backend_menus",
principalColumn: "code",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "fk_platform_backend_role_menus_platform_backend_roles_role_id",
column: x => x.role_id,
principalTable: "platform_backend_roles",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "announcements",
columns: table => new
@@ -1087,6 +1234,33 @@ namespace Tiku.Infrastructure.Persistence.Migrations
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "tenant_backend_roles",
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),
status = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
is_system = table.Column<bool>(type: "boolean", nullable: false),
description = table.Column<string>(type: "character varying(1000)", maxLength: 1000, nullable: true),
data_scope = 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_tenant_backend_roles", x => x.id);
table.UniqueConstraint("ak_tenant_backend_roles_tenant_id_id", x => new { x.tenant_id, x.id });
table.ForeignKey(
name: "fk_tenant_backend_roles_tenants_tenant_id",
column: x => x.tenant_id,
principalTable: "tenants",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "tenant_billing_profiles",
columns: table => new
@@ -2499,6 +2673,90 @@ namespace Tiku.Infrastructure.Persistence.Migrations
onDelete: ReferentialAction.SetNull);
});
migrationBuilder.CreateTable(
name: "tenant_backend_role_menus",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
role_id = table.Column<Guid>(type: "uuid", nullable: false),
menu_code = table.Column<string>(type: "character varying(160)", maxLength: 160, nullable: false),
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()")
},
constraints: table =>
{
table.PrimaryKey("pk_tenant_backend_role_menus", x => x.id);
table.UniqueConstraint("ak_tenant_backend_role_menus_tenant_id_id", x => new { x.tenant_id, x.id });
table.ForeignKey(
name: "fk_tenant_backend_role_menus_backend_menus_menu_code",
column: x => x.menu_code,
principalTable: "backend_menus",
principalColumn: "code",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "fk_tenant_backend_role_menus_tenant_backend_roles_tenant_id_ro~",
columns: x => new { x.tenant_id, x.role_id },
principalTable: "tenant_backend_roles",
principalColumns: new[] { "tenant_id", "id" },
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "tenant_backend_role_permissions",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
role_id = table.Column<Guid>(type: "uuid", nullable: false),
permission_code = table.Column<string>(type: "character varying(160)", maxLength: 160, nullable: false),
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()")
},
constraints: table =>
{
table.PrimaryKey("pk_tenant_backend_role_permissions", x => x.id);
table.UniqueConstraint("ak_tenant_backend_role_permissions_tenant_id_id", x => new { x.tenant_id, x.id });
table.ForeignKey(
name: "fk_tenant_backend_role_permissions_backend_permissions_permiss~",
column: x => x.permission_code,
principalTable: "backend_permissions",
principalColumn: "code",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "fk_tenant_backend_role_permissions_tenant_backend_roles_tenant~",
columns: x => new { x.tenant_id, x.role_id },
principalTable: "tenant_backend_roles",
principalColumns: new[] { "tenant_id", "id" },
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "tenant_backend_user_roles",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"),
tenant_id = table.Column<Guid>(type: "uuid", nullable: false),
user_id = table.Column<Guid>(type: "uuid", nullable: false),
role_id = table.Column<Guid>(type: "uuid", nullable: false),
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()")
},
constraints: table =>
{
table.PrimaryKey("pk_tenant_backend_user_roles", x => x.id);
table.UniqueConstraint("ak_tenant_backend_user_roles_tenant_id_id", x => new { x.tenant_id, x.id });
table.ForeignKey(
name: "fk_tenant_backend_user_roles_tenant_backend_roles_tenant_id_ro~",
columns: x => new { x.tenant_id, x.role_id },
principalTable: "tenant_backend_roles",
principalColumns: new[] { "tenant_id", "id" },
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_tenant_backend_user_roles_users_user_id",
column: x => x.user_id,
principalTable: "users",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "tenant_invoice_items",
columns: table => new
@@ -4799,6 +5057,46 @@ namespace Tiku.Infrastructure.Persistence.Migrations
onDelete: ReferentialAction.SetNull);
});
migrationBuilder.CreateTable(
name: "background_jobs",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"),
job_type = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
status = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
payload = table.Column<JsonElement>(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"),
retry_count = table.Column<int>(type: "integer", nullable: false),
max_retries = table.Column<int>(type: "integer", nullable: false),
locked_by = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: true),
lock_expires_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
run_after = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
started_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
completed_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
last_error = table.Column<string>(type: "character varying(4000)", maxLength: 4000, nullable: true),
output_asset_id = table.Column<Guid>(type: "uuid", nullable: true),
result = 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_background_jobs", x => x.id);
table.UniqueConstraint("ak_background_jobs_tenant_id_id", x => new { x.tenant_id, x.id });
table.ForeignKey(
name: "fk_background_jobs_content_assets_tenant_id_output_asset_id",
columns: x => new { x.tenant_id, x.output_asset_id },
principalTable: "content_assets",
principalColumns: new[] { "tenant_id", "id" },
onDelete: ReferentialAction.SetNull);
table.ForeignKey(
name: "fk_background_jobs_tenants_tenant_id",
column: x => x.tenant_id,
principalTable: "tenants",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "commission_settlement_proofs",
columns: table => new
@@ -6121,6 +6419,48 @@ namespace Tiku.Infrastructure.Persistence.Migrations
table: "auth_sessions",
column: "user_id");
migrationBuilder.CreateIndex(
name: "ix_backend_menus_area_parent_code_sort_order",
table: "backend_menus",
columns: new[] { "area", "parent_code", "sort_order" });
migrationBuilder.CreateIndex(
name: "ix_backend_menus_code",
table: "backend_menus",
column: "code",
unique: true);
migrationBuilder.CreateIndex(
name: "ix_backend_menus_permission_code",
table: "backend_menus",
column: "permission_code");
migrationBuilder.CreateIndex(
name: "ix_backend_permissions_area_module_sort_order",
table: "backend_permissions",
columns: new[] { "area", "module", "sort_order" });
migrationBuilder.CreateIndex(
name: "ix_backend_permissions_code",
table: "backend_permissions",
column: "code",
unique: true);
migrationBuilder.CreateIndex(
name: "ix_background_jobs_tenant_id_job_type_status_created_at",
table: "background_jobs",
columns: new[] { "tenant_id", "job_type", "status", "created_at" });
migrationBuilder.CreateIndex(
name: "ix_background_jobs_tenant_id_output_asset_id",
table: "background_jobs",
columns: new[] { "tenant_id", "output_asset_id" });
migrationBuilder.CreateIndex(
name: "ix_background_jobs_tenant_id_status_run_after_created_at",
table: "background_jobs",
columns: new[] { "tenant_id", "status", "run_after", "created_at" });
migrationBuilder.CreateIndex(
name: "ix_badges_tenant_id_category_is_active_sort_order",
table: "badges",
@@ -7129,6 +7469,50 @@ namespace Tiku.Infrastructure.Persistence.Migrations
table: "platform_audit_alerts",
columns: new[] { "tenant_id", "status", "created_at" });
migrationBuilder.CreateIndex(
name: "ix_platform_backend_role_menus_menu_code",
table: "platform_backend_role_menus",
column: "menu_code");
migrationBuilder.CreateIndex(
name: "ix_platform_backend_role_menus_role_id_menu_code",
table: "platform_backend_role_menus",
columns: new[] { "role_id", "menu_code" },
unique: true);
migrationBuilder.CreateIndex(
name: "ix_platform_backend_role_permissions_permission_code",
table: "platform_backend_role_permissions",
column: "permission_code");
migrationBuilder.CreateIndex(
name: "ix_platform_backend_role_permissions_role_id_permission_code",
table: "platform_backend_role_permissions",
columns: new[] { "role_id", "permission_code" },
unique: true);
migrationBuilder.CreateIndex(
name: "ix_platform_backend_roles_code",
table: "platform_backend_roles",
column: "code",
unique: true);
migrationBuilder.CreateIndex(
name: "ix_platform_backend_roles_status_code",
table: "platform_backend_roles",
columns: new[] { "status", "code" });
migrationBuilder.CreateIndex(
name: "ix_platform_backend_user_roles_role_id",
table: "platform_backend_user_roles",
column: "role_id");
migrationBuilder.CreateIndex(
name: "ix_platform_backend_user_roles_user_id_role_id",
table: "platform_backend_user_roles",
columns: new[] { "user_id", "role_id" },
unique: true);
migrationBuilder.CreateIndex(
name: "ix_platform_dunning_notification_channels_channel_code",
table: "platform_dunning_notification_channels",
@@ -7973,6 +8357,55 @@ namespace Tiku.Infrastructure.Persistence.Migrations
columns: new[] { "tenant_id", "code" },
unique: true);
migrationBuilder.CreateIndex(
name: "ix_tenant_backend_role_menus_menu_code",
table: "tenant_backend_role_menus",
column: "menu_code");
migrationBuilder.CreateIndex(
name: "ix_tenant_backend_role_menus_tenant_id_role_id_menu_code",
table: "tenant_backend_role_menus",
columns: new[] { "tenant_id", "role_id", "menu_code" },
unique: true);
migrationBuilder.CreateIndex(
name: "ix_tenant_backend_role_permissions_permission_code",
table: "tenant_backend_role_permissions",
column: "permission_code");
migrationBuilder.CreateIndex(
name: "ix_tenant_backend_role_permissions_tenant_id_role_id_permissio~",
table: "tenant_backend_role_permissions",
columns: new[] { "tenant_id", "role_id", "permission_code" },
unique: true);
migrationBuilder.CreateIndex(
name: "ix_tenant_backend_roles_tenant_id_code",
table: "tenant_backend_roles",
columns: new[] { "tenant_id", "code" },
unique: true);
migrationBuilder.CreateIndex(
name: "ix_tenant_backend_roles_tenant_id_status_code",
table: "tenant_backend_roles",
columns: new[] { "tenant_id", "status", "code" });
migrationBuilder.CreateIndex(
name: "ix_tenant_backend_user_roles_tenant_id_role_id",
table: "tenant_backend_user_roles",
columns: new[] { "tenant_id", "role_id" });
migrationBuilder.CreateIndex(
name: "ix_tenant_backend_user_roles_tenant_id_user_id_role_id",
table: "tenant_backend_user_roles",
columns: new[] { "tenant_id", "user_id", "role_id" },
unique: true);
migrationBuilder.CreateIndex(
name: "ix_tenant_backend_user_roles_user_id",
table: "tenant_backend_user_roles",
column: "user_id");
migrationBuilder.CreateIndex(
name: "ix_tenant_class_members_tenant_id_class_id_status_member_type",
table: "tenant_class_members",
@@ -8686,64 +9119,14 @@ namespace Tiku.Infrastructure.Persistence.Migrations
principalColumns: new[] { "tenant_id", "id" },
onDelete: ReferentialAction.SetNull);
migrationBuilder.Sql("""
create or replace function validate_tenant_question_reference()
returns trigger language plpgsql as $$
declare owner_mode text;
begin
select mode into owner_mode from tenants where id = new.question_owner_tenant_id;
if new.question_owner_tenant_id = new.tenant_id then
if new.source <> 'tenant' then raise exception 'A tenant-owned question reference must use tenant source'; end if;
elsif owner_mode = 'platform_owned' then
if new.source <> 'platform' then raise exception 'A platform-owned question reference must use platform source'; end if;
else
raise exception 'A tenant cannot reference another tenant private question';
end if;
return new;
end $$;
create trigger trg_validate_tenant_question_reference before insert or update on tenant_question_references
for each row execute function validate_tenant_question_reference();
create or replace function validate_taxonomy_owner_boundary()
returns trigger language plpgsql as $$
declare parent_mode text;
begin
if new.parent_owner_tenant_id is null or new.parent_owner_tenant_id = new.tenant_id then return new; end if;
select mode into parent_mode from tenants where id = new.parent_owner_tenant_id;
if parent_mode <> 'platform_owned' then
raise exception 'A taxonomy node can only extend platform or current tenant taxonomy';
end if;
return new;
end $$;
create trigger trg_validate_taxonomy_owner_boundary before insert or update on taxonomy_nodes
for each row execute function validate_taxonomy_owner_boundary();
create or replace function validate_question_bank_preference()
returns trigger language plpgsql as $$
declare owner_mode text;
begin
select mode into owner_mode from tenants where id = new.question_bank_owner_tenant_id;
if owner_mode <> 'platform_owned' then
raise exception 'Question bank preferences can only target platform-owned banks';
end if;
return new;
end $$;
create trigger trg_validate_question_bank_preference before insert or update on tenant_question_bank_preferences
for each row execute function validate_question_bank_preference();
""");
migrationBuilder.Sql(PostgreSqlTenantConstraintSql.CreateTenantQuestionReferenceGuard);
migrationBuilder.Sql(PostgreSqlTenantConstraintSql.CreateTaxonomyParentGuard);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql("""
drop trigger if exists trg_validate_question_bank_preference on tenant_question_bank_preferences;
drop function if exists validate_question_bank_preference();
drop trigger if exists trg_validate_taxonomy_owner_boundary on taxonomy_nodes;
drop function if exists validate_taxonomy_owner_boundary();
drop trigger if exists trg_validate_tenant_question_reference on tenant_question_references;
drop function if exists validate_tenant_question_reference();
""");
migrationBuilder.Sql(PostgreSqlTenantConstraintSql.DropTenantGuards);
migrationBuilder.DropForeignKey(
name: "fk_content_entries_regions_tenant_id_region_id",
@@ -8970,6 +9353,9 @@ namespace Tiku.Infrastructure.Persistence.Migrations
migrationBuilder.DropTable(
name: "auth_sessions");
migrationBuilder.DropTable(
name: "background_jobs");
migrationBuilder.DropTable(
name: "banners");
@@ -9042,6 +9428,15 @@ namespace Tiku.Infrastructure.Persistence.Migrations
migrationBuilder.DropTable(
name: "platform_audit_alerts");
migrationBuilder.DropTable(
name: "platform_backend_role_menus");
migrationBuilder.DropTable(
name: "platform_backend_role_permissions");
migrationBuilder.DropTable(
name: "platform_backend_user_roles");
migrationBuilder.DropTable(
name: "platform_dunning_notification_events");
@@ -9114,6 +9509,15 @@ namespace Tiku.Infrastructure.Persistence.Migrations
migrationBuilder.DropTable(
name: "subject_shares");
migrationBuilder.DropTable(
name: "tenant_backend_role_menus");
migrationBuilder.DropTable(
name: "tenant_backend_role_permissions");
migrationBuilder.DropTable(
name: "tenant_backend_user_roles");
migrationBuilder.DropTable(
name: "tenant_billing_profiles");
@@ -9225,6 +9629,9 @@ namespace Tiku.Infrastructure.Persistence.Migrations
migrationBuilder.DropTable(
name: "platform_audit_alert_rules");
migrationBuilder.DropTable(
name: "platform_backend_roles");
migrationBuilder.DropTable(
name: "platform_dunning_notification_channels");
@@ -9249,6 +9656,12 @@ namespace Tiku.Infrastructure.Persistence.Migrations
migrationBuilder.DropTable(
name: "reports");
migrationBuilder.DropTable(
name: "backend_menus");
migrationBuilder.DropTable(
name: "tenant_backend_roles");
migrationBuilder.DropTable(
name: "tenant_role_templates");
@@ -9285,6 +9698,9 @@ namespace Tiku.Infrastructure.Persistence.Migrations
migrationBuilder.DropTable(
name: "practice_sessions");
migrationBuilder.DropTable(
name: "backend_permissions");
migrationBuilder.DropTable(
name: "vocabulary_units");

View File

@@ -9876,6 +9876,268 @@ namespace Tiku.Infrastructure.Persistence.Migrations
b.ToTable("audit_logs", (string)null);
});
modelBuilder.Entity("Tiku.Domain.Operations.BackendMenu", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("id")
.HasDefaultValueSql("gen_random_uuid()");
b.Property<string>("Area")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("area");
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(160)
.HasColumnType("character varying(160)")
.HasColumnName("code");
b.Property<DateTimeOffset>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasDefaultValueSql("now()");
b.Property<string>("Icon")
.HasMaxLength(100)
.HasColumnType("character varying(100)")
.HasColumnName("icon");
b.Property<bool>("IsActive")
.HasColumnType("boolean")
.HasColumnName("is_active");
b.Property<string>("ParentCode")
.HasMaxLength(160)
.HasColumnType("character varying(160)")
.HasColumnName("parent_code");
b.Property<string>("Path")
.HasMaxLength(1024)
.HasColumnType("character varying(1024)")
.HasColumnName("path");
b.Property<string>("PermissionCode")
.HasMaxLength(160)
.HasColumnType("character varying(160)")
.HasColumnName("permission_code");
b.Property<int>("SortOrder")
.HasColumnType("integer")
.HasColumnName("sort_order");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)")
.HasColumnName("title");
b.Property<DateTimeOffset>("UpdatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at")
.HasDefaultValueSql("now()");
b.HasKey("Id")
.HasName("pk_backend_menus");
b.HasAlternateKey("Code")
.HasName("ak_backend_menus_code");
b.HasIndex("Code")
.IsUnique()
.HasDatabaseName("ix_backend_menus_code");
b.HasIndex("PermissionCode")
.HasDatabaseName("ix_backend_menus_permission_code");
b.HasIndex("Area", "ParentCode", "SortOrder")
.HasDatabaseName("ix_backend_menus_area_parent_code_sort_order");
b.ToTable("backend_menus", (string)null);
});
modelBuilder.Entity("Tiku.Domain.Operations.BackendPermission", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("id")
.HasDefaultValueSql("gen_random_uuid()");
b.Property<string>("Area")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("area");
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(160)
.HasColumnType("character varying(160)")
.HasColumnName("code");
b.Property<DateTimeOffset>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasDefaultValueSql("now()");
b.Property<string>("Description")
.HasMaxLength(1000)
.HasColumnType("character varying(1000)")
.HasColumnName("description");
b.Property<bool>("IsSystem")
.HasColumnType("boolean")
.HasColumnName("is_system");
b.Property<string>("Module")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)")
.HasColumnName("module");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)")
.HasColumnName("name");
b.Property<int>("SortOrder")
.HasColumnType("integer")
.HasColumnName("sort_order");
b.Property<DateTimeOffset>("UpdatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at")
.HasDefaultValueSql("now()");
b.HasKey("Id")
.HasName("pk_backend_permissions");
b.HasAlternateKey("Code")
.HasName("ak_backend_permissions_code");
b.HasIndex("Code")
.IsUnique()
.HasDatabaseName("ix_backend_permissions_code");
b.HasIndex("Area", "Module", "SortOrder")
.HasDatabaseName("ix_backend_permissions_area_module_sort_order");
b.ToTable("backend_permissions", (string)null);
});
modelBuilder.Entity("Tiku.Domain.Operations.BackgroundJob", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("id")
.HasDefaultValueSql("gen_random_uuid()");
b.Property<DateTimeOffset?>("CompletedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("completed_at");
b.Property<DateTimeOffset>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasDefaultValueSql("now()");
b.Property<string>("JobType")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)")
.HasColumnName("job_type");
b.Property<string>("LastError")
.HasMaxLength(4000)
.HasColumnType("character varying(4000)")
.HasColumnName("last_error");
b.Property<DateTimeOffset?>("LockExpiresAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("lock_expires_at");
b.Property<string>("LockedBy")
.HasMaxLength(200)
.HasColumnType("character varying(200)")
.HasColumnName("locked_by");
b.Property<int>("MaxRetries")
.HasColumnType("integer")
.HasColumnName("max_retries");
b.Property<Guid?>("OutputAssetId")
.HasColumnType("uuid")
.HasColumnName("output_asset_id");
b.Property<JsonElement>("Payload")
.ValueGeneratedOnAdd()
.HasColumnType("jsonb")
.HasColumnName("payload")
.HasDefaultValueSql("'{}'::jsonb");
b.Property<JsonElement>("Result")
.ValueGeneratedOnAdd()
.HasColumnType("jsonb")
.HasColumnName("result")
.HasDefaultValueSql("'{}'::jsonb");
b.Property<int>("RetryCount")
.HasColumnType("integer")
.HasColumnName("retry_count");
b.Property<DateTimeOffset?>("RunAfter")
.HasColumnType("timestamp with time zone")
.HasColumnName("run_after");
b.Property<DateTimeOffset?>("StartedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("started_at");
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.HasKey("Id")
.HasName("pk_background_jobs");
b.HasAlternateKey("TenantId", "Id")
.HasName("ak_background_jobs_tenant_id_id");
b.HasIndex("TenantId", "OutputAssetId")
.HasDatabaseName("ix_background_jobs_tenant_id_output_asset_id");
b.HasIndex("TenantId", "JobType", "Status", "CreatedAt")
.HasDatabaseName("ix_background_jobs_tenant_id_job_type_status_created_at");
b.HasIndex("TenantId", "Status", "RunAfter", "CreatedAt")
.HasDatabaseName("ix_background_jobs_tenant_id_status_run_after_created_at");
b.ToTable("background_jobs", (string)null);
});
modelBuilder.Entity("Tiku.Domain.Operations.Badge", b =>
{
b.Property<Guid>("Id")
@@ -10135,6 +10397,381 @@ namespace Tiku.Infrastructure.Persistence.Migrations
b.ToTable("faqs", (string)null);
});
modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendRole", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("id")
.HasDefaultValueSql("gen_random_uuid()");
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<string>("Description")
.HasMaxLength(1000)
.HasColumnType("character varying(1000)")
.HasColumnName("description");
b.Property<bool>("IsSystem")
.HasColumnType("boolean")
.HasColumnName("is_system");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)")
.HasColumnName("name");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("status");
b.Property<DateTimeOffset>("UpdatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at")
.HasDefaultValueSql("now()");
b.HasKey("Id")
.HasName("pk_platform_backend_roles");
b.HasIndex("Code")
.IsUnique()
.HasDatabaseName("ix_platform_backend_roles_code");
b.HasIndex("Status", "Code")
.HasDatabaseName("ix_platform_backend_roles_status_code");
b.ToTable("platform_backend_roles", (string)null);
});
modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendRoleMenu", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("id")
.HasDefaultValueSql("gen_random_uuid()");
b.Property<DateTimeOffset>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasDefaultValueSql("now()");
b.Property<string>("MenuCode")
.IsRequired()
.HasMaxLength(160)
.HasColumnType("character varying(160)")
.HasColumnName("menu_code");
b.Property<Guid>("RoleId")
.HasColumnType("uuid")
.HasColumnName("role_id");
b.HasKey("Id")
.HasName("pk_platform_backend_role_menus");
b.HasIndex("MenuCode")
.HasDatabaseName("ix_platform_backend_role_menus_menu_code");
b.HasIndex("RoleId", "MenuCode")
.IsUnique()
.HasDatabaseName("ix_platform_backend_role_menus_role_id_menu_code");
b.ToTable("platform_backend_role_menus", (string)null);
});
modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendRolePermission", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("id")
.HasDefaultValueSql("gen_random_uuid()");
b.Property<DateTimeOffset>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasDefaultValueSql("now()");
b.Property<string>("PermissionCode")
.IsRequired()
.HasMaxLength(160)
.HasColumnType("character varying(160)")
.HasColumnName("permission_code");
b.Property<Guid>("RoleId")
.HasColumnType("uuid")
.HasColumnName("role_id");
b.HasKey("Id")
.HasName("pk_platform_backend_role_permissions");
b.HasIndex("PermissionCode")
.HasDatabaseName("ix_platform_backend_role_permissions_permission_code");
b.HasIndex("RoleId", "PermissionCode")
.IsUnique()
.HasDatabaseName("ix_platform_backend_role_permissions_role_id_permission_code");
b.ToTable("platform_backend_role_permissions", (string)null);
});
modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendUserRole", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("id")
.HasDefaultValueSql("gen_random_uuid()");
b.Property<DateTimeOffset>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasDefaultValueSql("now()");
b.Property<Guid>("RoleId")
.HasColumnType("uuid")
.HasColumnName("role_id");
b.Property<Guid>("UserId")
.HasColumnType("uuid")
.HasColumnName("user_id");
b.HasKey("Id")
.HasName("pk_platform_backend_user_roles");
b.HasIndex("RoleId")
.HasDatabaseName("ix_platform_backend_user_roles_role_id");
b.HasIndex("UserId", "RoleId")
.IsUnique()
.HasDatabaseName("ix_platform_backend_user_roles_user_id_role_id");
b.ToTable("platform_backend_user_roles", (string)null);
});
modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRole", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("id")
.HasDefaultValueSql("gen_random_uuid()");
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<JsonElement>("DataScope")
.ValueGeneratedOnAdd()
.HasColumnType("jsonb")
.HasColumnName("data_scope")
.HasDefaultValueSql("'{}'::jsonb");
b.Property<string>("Description")
.HasMaxLength(1000)
.HasColumnType("character varying(1000)")
.HasColumnName("description");
b.Property<bool>("IsSystem")
.HasColumnType("boolean")
.HasColumnName("is_system");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)")
.HasColumnName("name");
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.HasKey("Id")
.HasName("pk_tenant_backend_roles");
b.HasAlternateKey("TenantId", "Id")
.HasName("ak_tenant_backend_roles_tenant_id_id");
b.HasIndex("TenantId", "Code")
.IsUnique()
.HasDatabaseName("ix_tenant_backend_roles_tenant_id_code");
b.HasIndex("TenantId", "Status", "Code")
.HasDatabaseName("ix_tenant_backend_roles_tenant_id_status_code");
b.ToTable("tenant_backend_roles", (string)null);
});
modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRoleMenu", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("id")
.HasDefaultValueSql("gen_random_uuid()");
b.Property<DateTimeOffset>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasDefaultValueSql("now()");
b.Property<string>("MenuCode")
.IsRequired()
.HasMaxLength(160)
.HasColumnType("character varying(160)")
.HasColumnName("menu_code");
b.Property<Guid>("RoleId")
.HasColumnType("uuid")
.HasColumnName("role_id");
b.Property<Guid>("TenantId")
.HasColumnType("uuid")
.HasColumnName("tenant_id");
b.HasKey("Id")
.HasName("pk_tenant_backend_role_menus");
b.HasAlternateKey("TenantId", "Id")
.HasName("ak_tenant_backend_role_menus_tenant_id_id");
b.HasIndex("MenuCode")
.HasDatabaseName("ix_tenant_backend_role_menus_menu_code");
b.HasIndex("TenantId", "RoleId", "MenuCode")
.IsUnique()
.HasDatabaseName("ix_tenant_backend_role_menus_tenant_id_role_id_menu_code");
b.ToTable("tenant_backend_role_menus", (string)null);
});
modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRolePermission", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("id")
.HasDefaultValueSql("gen_random_uuid()");
b.Property<DateTimeOffset>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasDefaultValueSql("now()");
b.Property<string>("PermissionCode")
.IsRequired()
.HasMaxLength(160)
.HasColumnType("character varying(160)")
.HasColumnName("permission_code");
b.Property<Guid>("RoleId")
.HasColumnType("uuid")
.HasColumnName("role_id");
b.Property<Guid>("TenantId")
.HasColumnType("uuid")
.HasColumnName("tenant_id");
b.HasKey("Id")
.HasName("pk_tenant_backend_role_permissions");
b.HasAlternateKey("TenantId", "Id")
.HasName("ak_tenant_backend_role_permissions_tenant_id_id");
b.HasIndex("PermissionCode")
.HasDatabaseName("ix_tenant_backend_role_permissions_permission_code");
b.HasIndex("TenantId", "RoleId", "PermissionCode")
.IsUnique()
.HasDatabaseName("ix_tenant_backend_role_permissions_tenant_id_role_id_permissio~");
b.ToTable("tenant_backend_role_permissions", (string)null);
});
modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendUserRole", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("id")
.HasDefaultValueSql("gen_random_uuid()");
b.Property<DateTimeOffset>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasDefaultValueSql("now()");
b.Property<Guid>("RoleId")
.HasColumnType("uuid")
.HasColumnName("role_id");
b.Property<Guid>("TenantId")
.HasColumnType("uuid")
.HasColumnName("tenant_id");
b.Property<Guid>("UserId")
.HasColumnType("uuid")
.HasColumnName("user_id");
b.HasKey("Id")
.HasName("pk_tenant_backend_user_roles");
b.HasAlternateKey("TenantId", "Id")
.HasName("ak_tenant_backend_user_roles_tenant_id_id");
b.HasIndex("UserId")
.HasDatabaseName("ix_tenant_backend_user_roles_user_id");
b.HasIndex("TenantId", "RoleId")
.HasDatabaseName("ix_tenant_backend_user_roles_tenant_id_role_id");
b.HasIndex("TenantId", "UserId", "RoleId")
.IsUnique()
.HasDatabaseName("ix_tenant_backend_user_roles_tenant_id_user_id_role_id");
b.ToTable("tenant_backend_user_roles", (string)null);
});
modelBuilder.Entity("Tiku.Domain.Operations.TenantContentNotification", b =>
{
b.Property<Guid>("Id")
@@ -15991,6 +16628,33 @@ namespace Tiku.Infrastructure.Persistence.Migrations
.HasConstraintName("fk_audit_logs_tenants_tenant_id");
});
modelBuilder.Entity("Tiku.Domain.Operations.BackendMenu", b =>
{
b.HasOne("Tiku.Domain.Operations.BackendPermission", null)
.WithMany()
.HasForeignKey("PermissionCode")
.HasPrincipalKey("Code")
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("fk_backend_menus_backend_permissions_permission_code");
});
modelBuilder.Entity("Tiku.Domain.Operations.BackgroundJob", b =>
{
b.HasOne("Tiku.Domain.Tenancy.Tenant", null)
.WithMany()
.HasForeignKey("TenantId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_background_jobs_tenants_tenant_id");
b.HasOne("Tiku.Domain.Content.ContentAsset", null)
.WithMany()
.HasForeignKey("TenantId", "OutputAssetId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("fk_background_jobs_content_assets_tenant_id_output_asset_id");
});
modelBuilder.Entity("Tiku.Domain.Operations.Badge", b =>
{
b.HasOne("Tiku.Domain.Tenancy.Tenant", null)
@@ -16035,6 +16699,125 @@ namespace Tiku.Infrastructure.Persistence.Migrations
.HasConstraintName("fk_faqs_regions_tenant_id_region_id");
});
modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendRoleMenu", b =>
{
b.HasOne("Tiku.Domain.Operations.BackendMenu", null)
.WithMany()
.HasForeignKey("MenuCode")
.HasPrincipalKey("Code")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired()
.HasConstraintName("fk_platform_backend_role_menus_backend_menus_menu_code");
b.HasOne("Tiku.Domain.Operations.PlatformBackendRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_platform_backend_role_menus_platform_backend_roles_role_id");
});
modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendRolePermission", b =>
{
b.HasOne("Tiku.Domain.Operations.BackendPermission", null)
.WithMany()
.HasForeignKey("PermissionCode")
.HasPrincipalKey("Code")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired()
.HasConstraintName("fk_platform_backend_role_permissions_backend_permissions_permi~");
b.HasOne("Tiku.Domain.Operations.PlatformBackendRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_platform_backend_role_permissions_platform_backend_roles_ro~");
});
modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendUserRole", b =>
{
b.HasOne("Tiku.Domain.Operations.PlatformBackendRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_platform_backend_user_roles_platform_backend_roles_role_id");
b.HasOne("Tiku.Domain.Identity.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_platform_backend_user_roles_users_user_id");
});
modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRole", b =>
{
b.HasOne("Tiku.Domain.Tenancy.Tenant", null)
.WithMany()
.HasForeignKey("TenantId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_tenant_backend_roles_tenants_tenant_id");
});
modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRoleMenu", b =>
{
b.HasOne("Tiku.Domain.Operations.BackendMenu", null)
.WithMany()
.HasForeignKey("MenuCode")
.HasPrincipalKey("Code")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired()
.HasConstraintName("fk_tenant_backend_role_menus_backend_menus_menu_code");
b.HasOne("Tiku.Domain.Operations.TenantBackendRole", null)
.WithMany()
.HasForeignKey("TenantId", "RoleId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_tenant_backend_role_menus_tenant_backend_roles_tenant_id_ro~");
});
modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRolePermission", b =>
{
b.HasOne("Tiku.Domain.Operations.BackendPermission", null)
.WithMany()
.HasForeignKey("PermissionCode")
.HasPrincipalKey("Code")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired()
.HasConstraintName("fk_tenant_backend_role_permissions_backend_permissions_permiss~");
b.HasOne("Tiku.Domain.Operations.TenantBackendRole", null)
.WithMany()
.HasForeignKey("TenantId", "RoleId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_tenant_backend_role_permissions_tenant_backend_roles_tenant~");
});
modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendUserRole", b =>
{
b.HasOne("Tiku.Domain.Identity.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_tenant_backend_user_roles_users_user_id");
b.HasOne("Tiku.Domain.Operations.TenantBackendRole", null)
.WithMany()
.HasForeignKey("TenantId", "RoleId")
.HasPrincipalKey("TenantId", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_tenant_backend_user_roles_tenant_backend_roles_tenant_id_ro~");
});
modelBuilder.Entity("Tiku.Domain.Operations.TenantContentNotification", b =>
{
b.HasOne("Tiku.Domain.Identity.User", null)

View File

@@ -0,0 +1,89 @@
namespace Tiku.Infrastructure.Persistence;
internal static class PostgreSqlTenantConstraintSql
{
public const string CreateTenantQuestionReferenceGuard = """
create or replace function tiku_guard_tenant_question_reference()
returns trigger
language plpgsql
as $$
declare
owner_mode text;
begin
select mode into owner_mode
from tenants
where id = new.question_owner_tenant_id;
if owner_mode = 'platform_owned' then
if new.source <> 'platform' then
raise exception 'tenant question reference to platform question must use platform source'
using errcode = '23514',
constraint = 'ck_tenant_question_references_platform_or_self_owner';
end if;
return new;
end if;
if new.question_owner_tenant_id = new.tenant_id then
if new.source <> 'tenant' then
raise exception 'tenant question reference to tenant-owned question must use tenant source'
using errcode = '23514',
constraint = 'ck_tenant_question_references_platform_or_self_owner';
end if;
return new;
end if;
raise exception 'tenant question reference cannot point to another tenant private question'
using errcode = '23514',
constraint = 'ck_tenant_question_references_platform_or_self_owner';
end;
$$;
create trigger trg_tenant_question_references_platform_or_self_owner
before insert or update of tenant_id, question_owner_tenant_id, question_id, source
on tenant_question_references
for each row
execute function tiku_guard_tenant_question_reference();
""";
public const string CreateTaxonomyParentGuard = """
create or replace function tiku_guard_taxonomy_parent_owner()
returns trigger
language plpgsql
as $$
declare
parent_owner_mode text;
begin
if new.parent_owner_tenant_id is null and new.parent_id is null then
return new;
end if;
select mode into parent_owner_mode
from tenants
where id = new.parent_owner_tenant_id;
if parent_owner_mode = 'platform_owned' or new.parent_owner_tenant_id = new.tenant_id then
return new;
end if;
raise exception 'taxonomy parent must belong to platform or current tenant'
using errcode = '23514',
constraint = 'ck_taxonomy_nodes_parent_platform_or_self_owner';
end;
$$;
create trigger trg_taxonomy_nodes_parent_platform_or_self_owner
before insert or update of tenant_id, parent_owner_tenant_id, parent_id
on taxonomy_nodes
for each row
execute function tiku_guard_taxonomy_parent_owner();
""";
public const string DropTenantGuards = """
drop trigger if exists trg_tenant_question_references_platform_or_self_owner on tenant_question_references;
drop function if exists tiku_guard_tenant_question_reference();
drop trigger if exists trg_taxonomy_nodes_parent_platform_or_self_owner on taxonomy_nodes;
drop function if exists tiku_guard_taxonomy_parent_owner();
""";
}

View File

@@ -153,6 +153,17 @@ public sealed class TikuDbContext(
public DbSet<Faq> Faqs => Set<Faq>();
public DbSet<Announcement> Announcements => Set<Announcement>();
public DbSet<AuditLog> AuditLogs => Set<AuditLog>();
public DbSet<BackendPermission> BackendPermissions => Set<BackendPermission>();
public DbSet<BackendMenu> BackendMenus => Set<BackendMenu>();
public DbSet<TenantBackendRole> TenantBackendRoles => Set<TenantBackendRole>();
public DbSet<TenantBackendRolePermission> TenantBackendRolePermissions => Set<TenantBackendRolePermission>();
public DbSet<TenantBackendRoleMenu> TenantBackendRoleMenus => Set<TenantBackendRoleMenu>();
public DbSet<TenantBackendUserRole> TenantBackendUserRoles => Set<TenantBackendUserRole>();
public DbSet<PlatformBackendRole> PlatformBackendRoles => Set<PlatformBackendRole>();
public DbSet<PlatformBackendRolePermission> PlatformBackendRolePermissions => Set<PlatformBackendRolePermission>();
public DbSet<PlatformBackendRoleMenu> PlatformBackendRoleMenus => Set<PlatformBackendRoleMenu>();
public DbSet<PlatformBackendUserRole> PlatformBackendUserRoles => Set<PlatformBackendUserRole>();
public DbSet<BackgroundJob> BackgroundJobs => Set<BackgroundJob>();
public DbSet<UserNotification> UserNotifications => Set<UserNotification>();
public DbSet<Badge> Badges => Set<Badge>();
public DbSet<UserBadge> UserBadges => Set<UserBadge>();