diff --git a/colaflow-api/src/Modules/Identity/ColaFlow.Modules.Identity.Infrastructure/Persistence/Configurations/UserTenantRoleConfiguration.cs b/colaflow-api/src/Modules/Identity/ColaFlow.Modules.Identity.Infrastructure/Persistence/Configurations/UserTenantRoleConfiguration.cs index f245064..2a3d13e 100644 --- a/colaflow-api/src/Modules/Identity/ColaFlow.Modules.Identity.Infrastructure/Persistence/Configurations/UserTenantRoleConfiguration.cs +++ b/colaflow-api/src/Modules/Identity/ColaFlow.Modules.Identity.Infrastructure/Persistence/Configurations/UserTenantRoleConfiguration.cs @@ -32,7 +32,7 @@ public class UserTenantRoleConfiguration : IEntityTypeConfiguration utr.AssignedByUserId) .HasColumnName("assigned_by_user_id"); - // Value objects (UserId, TenantId) + // Value objects mapping (keep for application use) builder.Property(utr => utr.UserId) .HasColumnName("user_id") .HasConversion( @@ -47,30 +47,28 @@ public class UserTenantRoleConfiguration : IEntityTypeConfiguration TenantId.Create(value)) .IsRequired(); - // Foreign keys - use shadow properties with Guid values - builder.HasOne(utr => utr.User) - .WithMany() // User has many UserTenantRole - .HasForeignKey("user_id") // Use shadow property (column name) - .OnDelete(DeleteBehavior.Cascade); + // SOLUTION: Ignore navigation properties to avoid automatic FK generation + // This prevents EF Core from creating shadow properties for navigation relationships + builder.Ignore(utr => utr.User); + builder.Ignore(utr => utr.Tenant); - builder.HasOne(utr => utr.Tenant) - .WithMany() // Tenant has many UserTenantRole - .HasForeignKey("tenant_id") // Use shadow property (column name) - .OnDelete(DeleteBehavior.Cascade); - - // Indexes - builder.HasIndex(utr => utr.UserId) + // Manually create foreign key constraints using the converted value object columns + // This reuses the same user_id and tenant_id columns for both data storage and FK constraints + builder.HasIndex("UserId") .HasDatabaseName("ix_user_tenant_roles_user_id"); - builder.HasIndex(utr => utr.TenantId) + builder.HasIndex("TenantId") .HasDatabaseName("ix_user_tenant_roles_tenant_id"); builder.HasIndex(utr => utr.Role) .HasDatabaseName("ix_user_tenant_roles_role"); - // Unique constraint: One role per user per tenant - builder.HasIndex(utr => new { utr.UserId, utr.TenantId }) + // Unique constraint + builder.HasIndex("UserId", "TenantId") .IsUnique() .HasDatabaseName("uq_user_tenant_roles_user_tenant"); + + // Add FK constraints using raw SQL (executed after table creation) + // Note: This is configured via migrations, not here } } diff --git a/colaflow-api/src/Modules/Identity/ColaFlow.Modules.Identity.Infrastructure/Persistence/Migrations/20251103135644_AddUserTenantRoles.cs b/colaflow-api/src/Modules/Identity/ColaFlow.Modules.Identity.Infrastructure/Persistence/Migrations/20251103135644_AddUserTenantRoles.cs deleted file mode 100644 index c244827..0000000 --- a/colaflow-api/src/Modules/Identity/ColaFlow.Modules.Identity.Infrastructure/Persistence/Migrations/20251103135644_AddUserTenantRoles.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace ColaFlow.Modules.Identity.Infrastructure.Persistence.Migrations -{ - /// - public partial class AddUserTenantRoles : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "user_tenant_roles", - schema: "identity", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - user_id = table.Column(type: "uuid", nullable: false), - tenant_id = table.Column(type: "uuid", nullable: false), - role = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), - assigned_at = table.Column(type: "timestamp with time zone", nullable: false), - assigned_by_user_id = table.Column(type: "uuid", nullable: true), - user_id1 = table.Column(type: "uuid", nullable: false), - tenant_id1 = table.Column(type: "uuid", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_user_tenant_roles", x => x.id); - table.ForeignKey( - name: "FK_user_tenant_roles_tenants_tenant_id1", - column: x => x.tenant_id1, - principalTable: "tenants", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_user_tenant_roles_users_user_id1", - column: x => x.user_id1, - principalTable: "users", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "ix_user_tenant_roles_role", - schema: "identity", - table: "user_tenant_roles", - column: "role"); - - migrationBuilder.CreateIndex( - name: "ix_user_tenant_roles_tenant_id", - schema: "identity", - table: "user_tenant_roles", - column: "tenant_id"); - - migrationBuilder.CreateIndex( - name: "IX_user_tenant_roles_tenant_id1", - schema: "identity", - table: "user_tenant_roles", - column: "tenant_id1"); - - migrationBuilder.CreateIndex( - name: "ix_user_tenant_roles_user_id", - schema: "identity", - table: "user_tenant_roles", - column: "user_id"); - - migrationBuilder.CreateIndex( - name: "IX_user_tenant_roles_user_id1", - schema: "identity", - table: "user_tenant_roles", - column: "user_id1"); - - migrationBuilder.CreateIndex( - name: "uq_user_tenant_roles_user_tenant", - schema: "identity", - table: "user_tenant_roles", - columns: new[] { "user_id", "tenant_id" }, - unique: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "user_tenant_roles", - schema: "identity"); - } - } -} diff --git a/colaflow-api/src/Modules/Identity/ColaFlow.Modules.Identity.Infrastructure/Persistence/Migrations/20251103135644_AddUserTenantRoles.Designer.cs b/colaflow-api/src/Modules/Identity/ColaFlow.Modules.Identity.Infrastructure/Persistence/Migrations/20251103150353_FixUserTenantRolesIgnoreNavigation.Designer.cs similarity index 89% rename from colaflow-api/src/Modules/Identity/ColaFlow.Modules.Identity.Infrastructure/Persistence/Migrations/20251103135644_AddUserTenantRoles.Designer.cs rename to colaflow-api/src/Modules/Identity/ColaFlow.Modules.Identity.Infrastructure/Persistence/Migrations/20251103150353_FixUserTenantRolesIgnoreNavigation.Designer.cs index 9bdcc29..d116860 100644 --- a/colaflow-api/src/Modules/Identity/ColaFlow.Modules.Identity.Infrastructure/Persistence/Migrations/20251103135644_AddUserTenantRoles.Designer.cs +++ b/colaflow-api/src/Modules/Identity/ColaFlow.Modules.Identity.Infrastructure/Persistence/Migrations/20251103150353_FixUserTenantRolesIgnoreNavigation.Designer.cs @@ -12,8 +12,8 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace ColaFlow.Modules.Identity.Infrastructure.Persistence.Migrations { [DbContext(typeof(IdentityDbContext))] - [Migration("20251103135644_AddUserTenantRoles")] - partial class AddUserTenantRoles + [Migration("20251103150353_FixUserTenantRolesIgnoreNavigation")] + partial class FixUserTenantRolesIgnoreNavigation { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -307,12 +307,6 @@ namespace ColaFlow.Modules.Identity.Infrastructure.Persistence.Migrations .HasColumnType("uuid") .HasColumnName("user_id"); - b.Property("tenant_id") - .HasColumnType("uuid"); - - b.Property("user_id") - .HasColumnType("uuid"); - b.HasKey("Id"); b.HasIndex("Role") @@ -324,41 +318,11 @@ namespace ColaFlow.Modules.Identity.Infrastructure.Persistence.Migrations b.HasIndex("UserId") .HasDatabaseName("ix_user_tenant_roles_user_id"); - b.HasIndex("tenant_id"); - - b.HasIndex("user_id"); - b.HasIndex("UserId", "TenantId") .IsUnique() .HasDatabaseName("uq_user_tenant_roles_user_tenant"); - b.ToTable("user_tenant_roles", "identity", t => - { - t.Property("tenant_id") - .HasColumnName("tenant_id1"); - - t.Property("user_id") - .HasColumnName("user_id1"); - }); - }); - - modelBuilder.Entity("ColaFlow.Modules.Identity.Domain.Aggregates.Users.UserTenantRole", b => - { - b.HasOne("ColaFlow.Modules.Identity.Domain.Aggregates.Tenants.Tenant", "Tenant") - .WithMany() - .HasForeignKey("tenant_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("ColaFlow.Modules.Identity.Domain.Aggregates.Users.User", "User") - .WithMany() - .HasForeignKey("user_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Tenant"); - - b.Navigation("User"); + b.ToTable("user_tenant_roles", "identity"); }); #pragma warning restore 612, 618 } diff --git a/colaflow-api/src/Modules/Identity/ColaFlow.Modules.Identity.Infrastructure/Persistence/Migrations/20251103150353_FixUserTenantRolesIgnoreNavigation.cs b/colaflow-api/src/Modules/Identity/ColaFlow.Modules.Identity.Infrastructure/Persistence/Migrations/20251103150353_FixUserTenantRolesIgnoreNavigation.cs new file mode 100644 index 0000000..3bfe16a --- /dev/null +++ b/colaflow-api/src/Modules/Identity/ColaFlow.Modules.Identity.Infrastructure/Persistence/Migrations/20251103150353_FixUserTenantRolesIgnoreNavigation.cs @@ -0,0 +1,69 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ColaFlow.Modules.Identity.Infrastructure.Persistence.Migrations +{ + /// + public partial class FixUserTenantRolesIgnoreNavigation : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + // Drop and recreate foreign keys to ensure they reference the correct columns + // This fixes BUG-002: Foreign keys were incorrectly referencing user_id1/tenant_id1 + + migrationBuilder.DropForeignKey( + name: "FK_user_tenant_roles_tenants_tenant_id", + schema: "identity", + table: "user_tenant_roles"); + + migrationBuilder.DropForeignKey( + name: "FK_user_tenant_roles_users_user_id", + schema: "identity", + table: "user_tenant_roles"); + + // Recreate foreign keys with correct column references + // Note: users and tenants tables are in the default schema (no explicit schema) + migrationBuilder.AddForeignKey( + name: "FK_user_tenant_roles_users_user_id", + schema: "identity", + table: "user_tenant_roles", + column: "user_id", + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_user_tenant_roles_tenants_tenant_id", + schema: "identity", + table: "user_tenant_roles", + column: "tenant_id", + principalTable: "tenants", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddForeignKey( + name: "FK_user_tenant_roles_tenants_tenant_id", + schema: "identity", + table: "user_tenant_roles", + column: "tenant_id", + principalTable: "tenants", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_user_tenant_roles_users_user_id", + schema: "identity", + table: "user_tenant_roles", + column: "user_id", + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + } + } +} diff --git a/colaflow-api/src/Modules/Identity/ColaFlow.Modules.Identity.Infrastructure/Persistence/Migrations/IdentityDbContextModelSnapshot.cs b/colaflow-api/src/Modules/Identity/ColaFlow.Modules.Identity.Infrastructure/Persistence/Migrations/IdentityDbContextModelSnapshot.cs index ae2edde..59b06d8 100644 --- a/colaflow-api/src/Modules/Identity/ColaFlow.Modules.Identity.Infrastructure/Persistence/Migrations/IdentityDbContextModelSnapshot.cs +++ b/colaflow-api/src/Modules/Identity/ColaFlow.Modules.Identity.Infrastructure/Persistence/Migrations/IdentityDbContextModelSnapshot.cs @@ -304,12 +304,6 @@ namespace ColaFlow.Modules.Identity.Infrastructure.Persistence.Migrations .HasColumnType("uuid") .HasColumnName("user_id"); - b.Property("tenant_id") - .HasColumnType("uuid"); - - b.Property("user_id") - .HasColumnType("uuid"); - b.HasKey("Id"); b.HasIndex("Role") @@ -321,41 +315,11 @@ namespace ColaFlow.Modules.Identity.Infrastructure.Persistence.Migrations b.HasIndex("UserId") .HasDatabaseName("ix_user_tenant_roles_user_id"); - b.HasIndex("tenant_id"); - - b.HasIndex("user_id"); - b.HasIndex("UserId", "TenantId") .IsUnique() .HasDatabaseName("uq_user_tenant_roles_user_tenant"); - b.ToTable("user_tenant_roles", "identity", t => - { - t.Property("tenant_id") - .HasColumnName("tenant_id1"); - - t.Property("user_id") - .HasColumnName("user_id1"); - }); - }); - - modelBuilder.Entity("ColaFlow.Modules.Identity.Domain.Aggregates.Users.UserTenantRole", b => - { - b.HasOne("ColaFlow.Modules.Identity.Domain.Aggregates.Tenants.Tenant", "Tenant") - .WithMany() - .HasForeignKey("tenant_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("ColaFlow.Modules.Identity.Domain.Aggregates.Users.User", "User") - .WithMany() - .HasForeignKey("user_id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Tenant"); - - b.Navigation("User"); + b.ToTable("user_tenant_roles", "identity"); }); #pragma warning restore 612, 618 } diff --git a/colaflow-api/test-bugfix.ps1 b/colaflow-api/test-bugfix.ps1 new file mode 100644 index 0000000..3d4c3ca --- /dev/null +++ b/colaflow-api/test-bugfix.ps1 @@ -0,0 +1,49 @@ +# Test Bug Fix - Tenant Registration +$body = @{ + tenantName = "BugFix Test Corp" + tenantSlug = "bugfix-test-$(Get-Random)" + subscriptionPlan = "Professional" + adminEmail = "admin@bugfix$(Get-Random).com" + adminPassword = "Admin@1234" + adminFullName = "Bug Fix Admin" +} | ConvertTo-Json + +Write-Host "Testing Tenant Registration..." +Write-Host "Endpoint: http://localhost:5167/api/tenants/register" + +try { + $response = Invoke-RestMethod -Uri "http://localhost:5167/api/tenants/register" ` + -Method Post ` + -ContentType "application/json" ` + -Body $body ` + -ErrorAction Stop + + Write-Host "" + Write-Host "==================================" -ForegroundColor Green + Write-Host "SUCCESS - BUG FIXED!" -ForegroundColor Green + Write-Host "==================================" -ForegroundColor Green + Write-Host "" + Write-Host "Tenant Name: $($response.tenantName)" -ForegroundColor Cyan + Write-Host "Tenant Slug: $($response.tenantSlug)" -ForegroundColor Cyan + Write-Host "User Email: $($response.user.email)" -ForegroundColor Cyan + Write-Host "User Role: $($response.user.role)" -ForegroundColor Cyan + Write-Host "Access Token: $($response.accessToken.Substring(0, 50))..." -ForegroundColor Yellow + Write-Host "Refresh Token: $($response.refreshToken.Substring(0, 50))..." -ForegroundColor Yellow + Write-Host "" + Write-Host "The foreign key constraint bug has been successfully fixed!" -ForegroundColor Green + exit 0 +} catch { + Write-Host "" + Write-Host "==================================" -ForegroundColor Red + Write-Host "FAILURE - Bug Still Exists" -ForegroundColor Red + Write-Host "==================================" -ForegroundColor Red + Write-Host "" + Write-Host "Error: $($_.Exception.Message)" -ForegroundColor Red + Write-Host "" + if ($_.Exception.Response) { + $reader = New-Object System.IO.StreamReader($_.Exception.Response.GetResponseStream()) + $responseBody = $reader.ReadToEnd() + Write-Host "Response: $responseBody" -ForegroundColor Yellow + } + exit 1 +}