fix(backend): Make UserTenantRoles migration idempotent to fix database initialization
Fixed BUG-007 where database migrations failed during initialization because the user_tenant_roles table was never created by any migration, but a later migration tried to modify it. Root Cause: - The user_tenant_roles table was configured in IdentityDbContext but missing from InitialIdentityModule migration - Migration 20251103150353_FixUserTenantRolesIgnoreNavigation tried to drop/recreate foreign keys on a non-existent table - This caused application startup to fail with "relation user_tenant_roles does not exist" Solution: - Made the migration idempotent by checking table existence before operations - If table doesn't exist, create it with proper schema, indexes, and constraints - Drop foreign keys only if they exist (safe for both first run and re-runs) - Corrected principal schema references (users/tenants are in default schema at this migration point) - Removed duplicate ix_user_tenant_roles_tenant_role index (created by later migration) Testing: - Clean database initialization: ✅ SUCCESS - All migrations applied successfully: ✅ SUCCESS - Application starts and listens: ✅ SUCCESS - Foreign keys created correctly: ✅ SUCCESS Impact: - Fixes P0 CRITICAL bug blocking Docker environment delivery - Enables clean database initialization from scratch - Maintains backward compatibility with existing databases 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -10,21 +10,68 @@ namespace ColaFlow.Modules.Identity.Infrastructure.Persistence.Migrations
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
{
|
{
|
||||||
// Drop and recreate foreign keys to ensure they reference the correct columns
|
// IDEMPOTENT FIX: Check if table exists before modifying it
|
||||||
// This fixes BUG-002: Foreign keys were incorrectly referencing user_id1/tenant_id1
|
// If the table doesn't exist, create it first
|
||||||
|
migrationBuilder.Sql(@"
|
||||||
|
-- Create user_tenant_roles table if it doesn't exist
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT FROM information_schema.tables
|
||||||
|
WHERE table_schema = 'identity'
|
||||||
|
AND table_name = 'user_tenant_roles'
|
||||||
|
) THEN
|
||||||
|
-- Create the table
|
||||||
|
CREATE TABLE identity.user_tenant_roles (
|
||||||
|
id uuid NOT NULL PRIMARY KEY,
|
||||||
|
user_id uuid NOT NULL,
|
||||||
|
tenant_id uuid NOT NULL,
|
||||||
|
role character varying(50) NOT NULL,
|
||||||
|
assigned_at timestamp with time zone NOT NULL,
|
||||||
|
assigned_by_user_id uuid,
|
||||||
|
CONSTRAINT uq_user_tenant_roles_user_tenant UNIQUE (user_id, tenant_id)
|
||||||
|
);
|
||||||
|
|
||||||
migrationBuilder.DropForeignKey(
|
-- Create basic indexes
|
||||||
name: "FK_user_tenant_roles_tenants_tenant_id",
|
-- Note: ix_user_tenant_roles_tenant_role will be created by a later migration
|
||||||
schema: "identity",
|
CREATE INDEX ix_user_tenant_roles_user_id ON identity.user_tenant_roles(user_id);
|
||||||
table: "user_tenant_roles");
|
CREATE INDEX ix_user_tenant_roles_tenant_id ON identity.user_tenant_roles(tenant_id);
|
||||||
|
CREATE INDEX ix_user_tenant_roles_role ON identity.user_tenant_roles(role);
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
");
|
||||||
|
|
||||||
migrationBuilder.DropForeignKey(
|
// Drop existing foreign keys if they exist
|
||||||
name: "FK_user_tenant_roles_users_user_id",
|
migrationBuilder.Sql(@"
|
||||||
schema: "identity",
|
DO $$
|
||||||
table: "user_tenant_roles");
|
BEGIN
|
||||||
|
-- Drop FK to tenants if it exists
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT FROM information_schema.table_constraints
|
||||||
|
WHERE constraint_schema = 'identity'
|
||||||
|
AND table_name = 'user_tenant_roles'
|
||||||
|
AND constraint_name = 'FK_user_tenant_roles_tenants_tenant_id'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE identity.user_tenant_roles
|
||||||
|
DROP CONSTRAINT ""FK_user_tenant_roles_tenants_tenant_id"";
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Drop FK to users if it exists
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT FROM information_schema.table_constraints
|
||||||
|
WHERE constraint_schema = 'identity'
|
||||||
|
AND table_name = 'user_tenant_roles'
|
||||||
|
AND constraint_name = 'FK_user_tenant_roles_users_user_id'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE identity.user_tenant_roles
|
||||||
|
DROP CONSTRAINT ""FK_user_tenant_roles_users_user_id"";
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
");
|
||||||
|
|
||||||
// Recreate foreign keys with correct column references
|
// Recreate foreign keys with correct column references
|
||||||
// Note: users and tenants tables are in the default schema (no explicit schema)
|
// Note: At this point in time, users and tenants are still in the default schema
|
||||||
|
// (They will be moved to identity schema in a later migration)
|
||||||
migrationBuilder.AddForeignKey(
|
migrationBuilder.AddForeignKey(
|
||||||
name: "FK_user_tenant_roles_users_user_id",
|
name: "FK_user_tenant_roles_users_user_id",
|
||||||
schema: "identity",
|
schema: "identity",
|
||||||
@@ -47,23 +94,35 @@ namespace ColaFlow.Modules.Identity.Infrastructure.Persistence.Migrations
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
{
|
{
|
||||||
migrationBuilder.AddForeignKey(
|
// Drop foreign keys if they exist
|
||||||
name: "FK_user_tenant_roles_tenants_tenant_id",
|
migrationBuilder.Sql(@"
|
||||||
schema: "identity",
|
DO $$
|
||||||
table: "user_tenant_roles",
|
BEGIN
|
||||||
column: "tenant_id",
|
IF EXISTS (
|
||||||
principalTable: "tenants",
|
SELECT FROM information_schema.table_constraints
|
||||||
principalColumn: "id",
|
WHERE constraint_schema = 'identity'
|
||||||
onDelete: ReferentialAction.Cascade);
|
AND table_name = 'user_tenant_roles'
|
||||||
|
AND constraint_name = 'FK_user_tenant_roles_tenants_tenant_id'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE identity.user_tenant_roles
|
||||||
|
DROP CONSTRAINT ""FK_user_tenant_roles_tenants_tenant_id"";
|
||||||
|
END IF;
|
||||||
|
|
||||||
migrationBuilder.AddForeignKey(
|
IF EXISTS (
|
||||||
name: "FK_user_tenant_roles_users_user_id",
|
SELECT FROM information_schema.table_constraints
|
||||||
schema: "identity",
|
WHERE constraint_schema = 'identity'
|
||||||
table: "user_tenant_roles",
|
AND table_name = 'user_tenant_roles'
|
||||||
column: "user_id",
|
AND constraint_name = 'FK_user_tenant_roles_users_user_id'
|
||||||
principalTable: "users",
|
) THEN
|
||||||
principalColumn: "id",
|
ALTER TABLE identity.user_tenant_roles
|
||||||
onDelete: ReferentialAction.Cascade);
|
DROP CONSTRAINT ""FK_user_tenant_roles_users_user_id"";
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
");
|
||||||
|
|
||||||
|
// Note: We don't drop the table in Down() because it should have been created
|
||||||
|
// by a previous migration. If it was created by this migration (first run),
|
||||||
|
// then it will be cleaned up when the database is reset.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user