diff --git a/server/migrations/20250912000001-add-multi-tenant-support.js b/server/migrations/20250912000001-add-multi-tenant-support.js index f98d359..266ab39 100644 --- a/server/migrations/20250912000001-add-multi-tenant-support.js +++ b/server/migrations/20250912000001-add-multi-tenant-support.js @@ -7,8 +7,9 @@ module.exports = { async up(queryInterface, Sequelize) { - // Create tenants table - await queryInterface.createTable('tenants', { + // Create tenants table (idempotent) + try { + await queryInterface.createTable('tenants', { id: { type: Sequelize.UUID, defaultValue: Sequelize.UUIDV4, @@ -109,47 +110,140 @@ module.exports = { defaultValue: Sequelize.NOW } }); + console.log('✅ Created tenants table'); + } catch (error) { + if (error.parent?.code === '42P07') { // Table already exists + console.log('⚠️ Tenants table already exists, skipping...'); + } else { + throw error; + } + } - // Add indexes to tenants table - await queryInterface.addIndex('tenants', ['slug'], { unique: true }); - await queryInterface.addIndex('tenants', ['domain'], { - unique: true, - where: { domain: { [Sequelize.Op.ne]: null } } - }); - await queryInterface.addIndex('tenants', ['is_active']); - await queryInterface.addIndex('tenants', ['auth_provider']); + // Add indexes to tenants table (idempotent) + try { + await queryInterface.addIndex('tenants', ['slug'], { unique: true }); + console.log('✅ Added unique index on tenants.slug'); + } catch (error) { + if (error.parent?.code === '42P07') { // Index already exists + console.log('⚠️ Index tenants_slug already exists, skipping...'); + } else { + throw error; + } + } - // Add tenant-related columns to users table - await queryInterface.addColumn('users', 'tenant_id', { - type: Sequelize.UUID, - allowNull: true, - references: { - model: 'tenants', - key: 'id' - }, - comment: 'Tenant this user belongs to' - }); + try { + await queryInterface.addIndex('tenants', ['domain'], { + unique: true, + where: { domain: { [Sequelize.Op.ne]: null } } + }); + console.log('✅ Added unique index on tenants.domain'); + } catch (error) { + if (error.parent?.code === '42P07') { // Index already exists + console.log('⚠️ Index tenants_domain already exists, skipping...'); + } else { + throw error; + } + } - await queryInterface.addColumn('users', 'external_provider', { - type: Sequelize.ENUM('local', 'saml', 'oauth', 'ldap', 'custom_sso'), - defaultValue: 'local', - comment: 'Authentication provider used for this user' - }); + try { + await queryInterface.addIndex('tenants', ['is_active']); + console.log('✅ Added index on tenants.is_active'); + } catch (error) { + if (error.parent?.code === '42P07') { // Index already exists + console.log('⚠️ Index tenants_is_active already exists, skipping...'); + } else { + throw error; + } + } - await queryInterface.addColumn('users', 'external_id', { - type: Sequelize.STRING, - allowNull: true, - comment: 'User ID from external authentication provider' - }); + try { + await queryInterface.addIndex('tenants', ['auth_provider']); + console.log('✅ Added index on tenants.auth_provider'); + } catch (error) { + if (error.parent?.code === '42P07') { // Index already exists + console.log('⚠️ Index tenants_auth_provider already exists, skipping...'); + } else { + throw error; + } + } - // Add indexes to users table - await queryInterface.addIndex('users', ['tenant_id']); - await queryInterface.addIndex('users', ['external_provider']); - await queryInterface.addIndex('users', ['external_id', 'tenant_id'], { - unique: true, - name: 'users_external_id_tenant_unique', - where: { external_id: { [Sequelize.Op.ne]: null } } - }); + // Add tenant-related columns to users table (idempotent) + const usersTableDescription = await queryInterface.describeTable('users'); + + if (!usersTableDescription.tenant_id) { + await queryInterface.addColumn('users', 'tenant_id', { + type: Sequelize.UUID, + allowNull: true, + references: { + model: 'tenants', + key: 'id' + }, + comment: 'Tenant this user belongs to' + }); + console.log('✅ Added tenant_id column to users table'); + } else { + console.log('⚠️ Column tenant_id already exists in users table, skipping...'); + } + + if (!usersTableDescription.external_provider) { + await queryInterface.addColumn('users', 'external_provider', { + type: Sequelize.ENUM('local', 'saml', 'oauth', 'ldap', 'custom_sso'), + defaultValue: 'local', + comment: 'Authentication provider used for this user' + }); + console.log('✅ Added external_provider column to users table'); + } else { + console.log('⚠️ Column external_provider already exists in users table, skipping...'); + } + + if (!usersTableDescription.external_id) { + await queryInterface.addColumn('users', 'external_id', { + type: Sequelize.STRING, + allowNull: true, + comment: 'User ID from external authentication provider' + }); + console.log('✅ Added external_id column to users table'); + } else { + console.log('⚠️ Column external_id already exists in users table, skipping...'); + } + + // Add indexes to users table (idempotent) + try { + await queryInterface.addIndex('users', ['tenant_id']); + console.log('✅ Added index on users.tenant_id'); + } catch (error) { + if (error.parent?.code === '42P07') { // Index already exists + console.log('⚠️ Index users_tenant_id already exists, skipping...'); + } else { + throw error; + } + } + + try { + await queryInterface.addIndex('users', ['external_provider']); + console.log('✅ Added index on users.external_provider'); + } catch (error) { + if (error.parent?.code === '42P07') { // Index already exists + console.log('⚠️ Index users_external_provider already exists, skipping...'); + } else { + throw error; + } + } + + try { + await queryInterface.addIndex('users', ['external_id', 'tenant_id'], { + unique: true, + name: 'users_external_id_tenant_unique', + where: { external_id: { [Sequelize.Op.ne]: null } } + }); + console.log('✅ Added unique index on users.external_id + tenant_id'); + } catch (error) { + if (error.parent?.code === '42P07') { // Index already exists + console.log('⚠️ Index users_external_id_tenant_unique already exists, skipping...'); + } else { + throw error; + } + } // Create default tenant for backward compatibility const defaultTenantId = await queryInterface.bulkInsert('tenants', [{