Skip to content

Conversation

@nevil-mathew
Copy link
Collaborator

@nevil-mathew nevil-mathew commented Sep 16, 2025

Summary by CodeRabbit

  • New Features

    • Automatically enables available features for the default organization.
    • Seeds a default organization alongside the default tenant and domain for smoother onboarding.
  • Bug Fixes

    • Normalizes organization codes to a consistent format, preventing issues caused by whitespace or casing differences.
  • Chores

    • Updates seed data to include organization codes for entities and entity types.
    • Consolidates and replaces earlier default tenant/domain seeding with a more robust, idempotent migration.
    • Adds safe checks and transactions to avoid duplicate data and ensure reliable rollbacks.

@coderabbitai
Copy link

coderabbitai bot commented Sep 16, 2025

Warning

Rate limit exceeded

@nevil-mathew has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 1 minutes and 34 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between ab06036 and d07d532.

📒 Files selected for processing (2)
  • src/database/migrations/20250506091446-create-deafult-tenants-and-domains.js (1 hunks)
  • src/database/migrations/20250916094307-add-organization-features.js (1 hunks)

Walkthrough

Removes an earlier default-tenant seeding migration, adds a new consolidated migration that seeds tenant, domain, organization, and organization_features idempotently, introduces a separate migration to seed organization_features from features, refactors the org-code normalization migration (FK handling and signatures), and updates a seeder to include organization_code.

Changes

Cohort / File(s) Summary of changes
Tenant/org seeding migrations update
src/database/migrations/20250502091425-create-deafult-tenants-and-domains.js, src/database/migrations/20250506091446-create-deafult-tenants-and-domains.js
Removed legacy default-tenant/domain seeding migration; added new idempotent migration that seeds default tenant, domain, organization, and organization_features within a transaction, using env-derived codes/names and existence checks; includes reversible down.
Organization code normalization refactor
src/database/migrations/20250729064710-org-code-fix.js
Reworked migration to pre-check necessity, batch disable/recreate FKs, normalize organization codes across tables, simplify error handling; changed function signatures to up(queryInterface) and down().
New organization_features seeding
src/database/migrations/20250916094307-add-organization-features.js
New migration to seed organization_features for default org from existing features with idempotent checks in a transaction; includes down that deletes seeded rows for default org/tenant.
Seeder adds organization_code
src/database/seeders/20230802144103-add-entity-and-entity-types.js
Adds organization_code (from process.env.DEFAULT_ORGANISATION_CODE) to entity_types and entities during bulk inserts; no signature changes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Dev as Migration (20250506091446)
  participant DB as DB (queryInterface)

  rect rgba(180,220,255,0.25)
  note over Dev,DB: Up: Seed default tenant/domain/org/org_features (idempotent)
  Dev->>DB: BEGIN TRANSACTION
  Dev->>DB: SELECT tenant by DEFAULT_TENANT_CODE
  alt Tenant missing
    Dev->>DB: INSERT tenants (...)
  end
  Dev->>DB: SELECT tenant_domain localhost
  alt Domain missing
    Dev->>DB: INSERT tenant_domains (...)
  end
  Dev->>DB: SELECT organization by DEFAULT_ORG_CODE
  alt Org missing
    Dev->>DB: INSERT organizations (...)
  end
  Dev->>DB: SELECT features
  loop For each feature
    Dev->>DB: SELECT org_feature (org_code, feature_code, tenant_code)
    alt Missing link
      Dev->>DB: INSERT organization_features (...)
    end
  end
  Dev-->>DB: COMMIT
  end

  rect rgba(255,210,180,0.25)
  note over Dev,DB: Down: Remove seeded rows
  Dev->>DB: BEGIN TRANSACTION
  Dev->>DB: DELETE organization_features (org_code, tenant_code)
  Dev->>DB: DELETE organizations (code)
  Dev->>DB: DELETE tenant_domains (tenant_code)
  Dev->>DB: DELETE tenants (code)
  Dev-->>DB: COMMIT
  end
Loading
sequenceDiagram
  autonumber
  actor Dev as Migration (20250916094307)
  participant DB as DB (queryInterface)

  rect rgba(200,255,200,0.25)
  note over Dev,DB: Up: Seed organization_features from features
  Dev->>DB: BEGIN TRANSACTION
  Dev->>DB: SELECT organization by DEFAULT_ORG_CODE
  alt Org not found
    Dev-->>DB: COMMIT
    note right of Dev: Exit early
  else Org found
    Dev->>DB: SELECT all features
    alt No features
      Dev-->>DB: COMMIT
    else
      loop For each feature
        Dev->>DB: SELECT org_feature (org_code, feature_code, tenant_code)
        alt Missing
          Dev->>DB: Prepare row for bulk insert
        end
      end
      Dev->>DB: BULK INSERT organization_features (new rows)
      Dev-->>DB: COMMIT
    end
  end
  end

  rect rgba(255,230,200,0.25)
  note over Dev,DB: Down: Clean up links
  Dev->>DB: BEGIN TRANSACTION
  Dev->>DB: DELETE organization_features (DEFAULT or 'default' org/tenant)
  Dev-->>DB: COMMIT
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

Thump-thump! I seed and hop with glee,
Tenants, orgs, and features—whee!
FK burrows neatly lined,
Codes made tidy, underscored, aligned.
Carrot-shaped commits in rows appear—
Migration meadows, crystal-clear. 🥕✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly captures the primary work: refactoring the default tenant/domain migration and adding safety and idempotence improvements. The changeset removes an older default-tenant/domain migration and introduces a transactionally safe, existence-checked migration that seeds tenant, domain, organization, and organization_features, which aligns with the title. Supporting changes (org-code normalization and minor seeder updates) are secondary and do not make the title misleading.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/database/seeders/20230802144103-add-entity-and-entity-types.js (3)

171-183: Bug: entity label is being overwritten with tenant code.

This line mutates each entity and sets label = DEFAULT_TENANT_CODE, corrupting seed data (e.g., "English" becomes "default"). Also avoids cloning, so you mutate source templates in-place.

Apply this diff to fix by cloning and preserving label:

-    entitiesArray[eachType.value].forEach((eachEntity) => {
-      eachEntity.entity_type_id = eachType.id
-      eachEntity.type = 'SYSTEM'
-      eachEntity.status = 'ACTIVE'
-      eachEntity.tenant_code = process.env.DEFAULT_TENANT_CODE
-      eachEntity.organization_code = process.env.DEFAULT_ORGANISATION_CODE
-      eachEntity.created_at = new Date()
-      eachEntity.updated_at = new Date()
-      eachEntity.created_by = 0
-      ;(eachEntity.label = process.env.DEFAULT_TENANT_CODE), entitiesFinalArray.push(eachEntity)
-    })
+    entitiesArray[eachType.value].forEach((eachEntity) => {
+      const row = {
+        ...eachEntity, // keep original label/value
+        entity_type_id: eachType.id,
+        type: 'SYSTEM',
+        status: 'ACTIVE',
+        tenant_code: DEFAULT_TENANT_CODE,
+        organization_code: DEFAULT_ORG_CODE,
+        created_at: new Date(),
+        updated_at: new Date(),
+        created_by: 0,
+        updated_by: 0,
+      }
+      entitiesFinalArray.push(row)
+    })

2-7: Validate and bind env vars once; fail fast if missing.

Seeding will insert NULLs if envs aren’t set. Capture and validate them up-front; reuse variables instead of process.env inline to prevent drift.

   up: async (queryInterface, Sequelize) => {
     const defaultOrgId = queryInterface.sequelize.options.defaultOrgId
     if (!defaultOrgId) {
       throw new Error('Default org ID is undefined. Please make sure it is set in sequelize options.')
     }
+    const DEFAULT_TENANT_CODE = process.env.DEFAULT_TENANT_CODE
+    const DEFAULT_ORG_CODE = process.env.DEFAULT_ORGANISATION_CODE
+    if (!DEFAULT_TENANT_CODE || !DEFAULT_ORG_CODE) {
+      throw new Error('DEFAULT_TENANT_CODE and DEFAULT_ORGANISATION_CODE must be set for this seeder.')
+    }

Also replace inline usages:

-        organization_code: process.env.DEFAULT_ORGANISATION_CODE,
-        tenant_code: process.env.DEFAULT_TENANT_CODE,
+        organization_code: DEFAULT_ORG_CODE,
+        tenant_code: DEFAULT_TENANT_CODE,

and

-        eachEntity.tenant_code = process.env.DEFAULT_TENANT_CODE
-        eachEntity.organization_code = process.env.DEFAULT_ORGANISATION_CODE
+        eachEntity.tenant_code = DEFAULT_TENANT_CODE
+        eachEntity.organization_code = DEFAULT_ORG_CODE

165-167: Scope the entity_types lookup to the default org/tenant.

SELECT * FROM entity_types risks binding to rows from other orgs and duplicating entities. Filter by org/tenant or by the ids you just inserted.

-    const entityTypes = await queryInterface.sequelize.query('SELECT * FROM entity_types', {
-      type: queryInterface.sequelize.QueryTypes.SELECT,
-    })
+    const entityTypes = await queryInterface.sequelize.query(
+      'SELECT id, value FROM entity_types WHERE organization_id = :orgId AND tenant_code = :tenant',
+      {
+        type: queryInterface.sequelize.QueryTypes.SELECT,
+        replacements: { orgId: defaultOrgId, tenant: DEFAULT_TENANT_CODE },
+      }
+    )
🧹 Nitpick comments (7)
src/database/seeders/20230802144103-add-entity-and-entity-types.js (1)

134-164: Wrap seeding in a transaction for atomicity.

Two bulk inserts + read/compose without a transaction can leave partial data on failure.

If desired, I can provide a patch to wrap the inserts and SELECTs in a single transaction.

src/database/migrations/20250729064710-org-code-fix.js (1)

136-141: Assert post-update normalization succeeded.

You fetch remaining offenders but ignore the result. Fail fast if any rows still contain spaces/uppercase.

-  const fetchOrgs = await queryInterface.sequelize.query(ORG_FETCH_QUERY, {
+  const fetchOrgs = await queryInterface.sequelize.query(ORG_FETCH_QUERY, {
     type: QueryTypes.SELECT,
     raw: true,
     transaction,
   })
+  if (fetchOrgs.length > 0) {
+    throw new Error(`Organization code normalization incomplete for ${fetchOrgs.length} row(s).`)
+  }
src/database/migrations/20250506091446-create-deafult-tenants-and-domains.js (3)

78-99: Remove unused variable and stale comment.
sinsertedOrgCode is never read; the comment hints at an id fetch that doesn’t happen.

-  let insertedOrgCode = DEFAULT_ORG_CODE
-
-  if (!existingOrgId) {
+  if (!existingOrgId) {
     await queryInterface.bulkInsert(
       'organizations',
       [
         {
           name: DEFAULT_ORG_NAME,
           code: DEFAULT_ORG_CODE,
           description: 'Default Organisation',
           status: 'ACTIVE',
           tenant_code: DEFAULT_TENANT_CODE,
           created_at: now,
           updated_at: now,
         },
       ],
       { transaction: t }
     )
-
-    // get the id just inserted (DB may have returned id via serial; rawSelect by code ensures we get it)
   }

1-1: Filename typo: “deafult”.

Consider renaming to “create-default-tenants-and-domains.js” for clarity (ack: renaming migrations affects order; do only if safe).


100-141: Avoid duplicate feature seeding across two migrations.

This migration and 20250916094307 both seed organization_features. It’s idempotent but redundant.

Pick one authoritative migration (prefer the consolidated one) or guard the latter behind an existence check on this file’s timestamp/applied status.

src/database/migrations/20250916094307-add-organization-features.js (2)

38-66: Set-based insert will be faster and simpler.

Looping + many rawSelect calls is N+1. Prefer a single insert-select with ON CONFLICT DO NOTHING (if a unique constraint exists).

Example (Postgres):

INSERT INTO organization_features (organization_code, feature_code, enabled, feature_name, icon, tenant_code, created_at, updated_at, display_order)
SELECT :org, f.code, TRUE, f.label, f.icon, :tenant, NOW(), NOW(), f.display_order
FROM features f
LEFT JOIN organization_features of
  ON of.feature_code = f.code
 AND of.organization_code = :org
 AND of.tenant_code = :tenant
WHERE of.id IS NULL;

Happy to translate this to a queryInterface.sequelize.query call.

Also applies to: 68-70


1-99: Redundant with create-default-tenants-and-domains seeding.

Coordinate with 20250506091446 to avoid duplication; keep only one features seeding path.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f4e0475 and ab06036.

📒 Files selected for processing (5)
  • src/database/migrations/20250502091425-create-deafult-tenants-and-domains.js (0 hunks)
  • src/database/migrations/20250506091446-create-deafult-tenants-and-domains.js (1 hunks)
  • src/database/migrations/20250729064710-org-code-fix.js (1 hunks)
  • src/database/migrations/20250916094307-add-organization-features.js (1 hunks)
  • src/database/seeders/20230802144103-add-entity-and-entity-types.js (2 hunks)
💤 Files with no reviewable changes (1)
  • src/database/migrations/20250502091425-create-deafult-tenants-and-domains.js
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: nevil-mathew
PR: ELEVATE-Project/user#776
File: src/database/models/entityType.js:38-38
Timestamp: 2025-07-31T08:43:35.971Z
Learning: The migration for converting tenant_code to a primary key in the EntityType model was already handled in a previous PR, not in the current refactoring PR that focuses on organization codes instead of organization IDs.
📚 Learning: 2025-07-31T08:43:35.971Z
Learnt from: nevil-mathew
PR: ELEVATE-Project/user#776
File: src/database/models/entityType.js:38-38
Timestamp: 2025-07-31T08:43:35.971Z
Learning: The migration for converting tenant_code to a primary key in the EntityType model was already handled in a previous PR, not in the current refactoring PR that focuses on organization codes instead of organization IDs.

Applied to files:

  • src/database/seeders/20230802144103-add-entity-and-entity-types.js
  • src/database/migrations/20250729064710-org-code-fix.js
📚 Learning: 2025-09-12T10:35:28.285Z
Learnt from: praveenKDass
PR: ELEVATE-Project/user#816
File: src/services/admin.js:704-704
Timestamp: 2025-09-12T10:35:28.285Z
Learning: In the ELEVATE-Project/user codebase, when organizationQueries.update is called with { returning: true, raw: true }, it returns an object { rowsAffected, updatedRows }, not just a number. The validation logic in admin.js deactivateOrg method checking 'if (orgRowsAffected === 0)' is incorrect and should be 'if (orgRowsAffected.rowsAffected === 0)' to properly validate successful updates.

Applied to files:

  • src/database/migrations/20250729064710-org-code-fix.js
📚 Learning: 2025-09-12T10:35:28.285Z
Learnt from: praveenKDass
PR: ELEVATE-Project/user#816
File: src/services/admin.js:704-704
Timestamp: 2025-09-12T10:35:28.285Z
Learning: In ELEVATE-Project/user admin.js, there's an inconsistency in organizationQueries.update usage: addOrgAdmin calls it without options (returns number), while deactivateOrg calls it with { returning: true, raw: true } (returns object). The validation logic 'if (orgRowsAffected === 0)' only works for the first case - the second case needs 'if (orgRowsAffected.rowsAffected === 0)' to properly validate updates.

Applied to files:

  • src/database/migrations/20250729064710-org-code-fix.js
📚 Learning: 2025-09-12T10:35:28.285Z
Learnt from: praveenKDass
PR: ELEVATE-Project/user#816
File: src/services/admin.js:704-704
Timestamp: 2025-09-12T10:35:28.285Z
Learning: In the ELEVATE-Project/user codebase, when using organizationQueries.update with { returning: true, raw: true } options, the method consistently returns a structured result with updatedRows property when successful. The initial check for orgRowsAffected === 0 already handles failed updates, so additional null checking for updatedRows is redundant when the code execution reaches the event emission logic.

Applied to files:

  • src/database/migrations/20250729064710-org-code-fix.js
🧬 Code graph analysis (4)
src/database/migrations/20250916094307-add-organization-features.js (2)
src/database/migrations/20250506091446-create-deafult-tenants-and-domains.js (9)
  • now (5-5)
  • t (6-6)
  • t (152-152)
  • DEFAULT_TENANT_CODE (9-9)
  • DEFAULT_TENANT_CODE (153-153)
  • DEFAULT_ORG_CODE (10-10)
  • DEFAULT_ORG_CODE (154-154)
  • features (101-103)
  • exists (111-122)
src/database/models/index.js (1)
  • process (6-6)
src/database/seeders/20230802144103-add-entity-and-entity-types.js (1)
src/database/models/index.js (1)
  • process (6-6)
src/database/migrations/20250506091446-create-deafult-tenants-and-domains.js (1)
src/database/migrations/20250916094307-add-organization-features.js (9)
  • now (5-5)
  • t (6-6)
  • t (81-81)
  • DEFAULT_TENANT_CODE (8-8)
  • DEFAULT_TENANT_CODE (82-82)
  • DEFAULT_ORG_CODE (9-9)
  • DEFAULT_ORG_CODE (83-83)
  • features (26-29)
  • exists (40-51)
src/database/migrations/20250729064710-org-code-fix.js (1)
src/scripts/deleted-org-data-clean-up/clean.js (1)
  • require (1-1)
🔇 Additional comments (2)
src/database/seeders/20230802144103-add-entity-and-entity-types.js (1)

146-148: Ensure org_id and org_code refer to the same organization.

Mixing organization_id (from sequelize options) with organization_code (from env) can mismatch if configs drift.

Confirm that queryInterface.sequelize.options.defaultOrgId belongs to DEFAULT_ORGANISATION_CODE. If unsure, gate the seeder with a sanity check comparing (id, code) from organizations.

Also applies to: 178-178

src/database/migrations/20250729064710-org-code-fix.js (1)

29-57: Resolved — composite FKs to organizations(code, tenant_code) are valid.

Verified a UNIQUE/PK on (code, tenant_code) and that child tables include organization_code + tenant_code, so the composite FKs are supported. Evidence: src/database/migrations/20250506091445-update-user-org-tables.js (adds unique_org_code_per_tenant and adjusts PK to include tenant_code), src/database/migrations/20250623073422-organizations-normalize-registration-code.js (index + fk using (organization_code, tenant_code) -> organizations(code, tenant_code)), src/database/migrations/20250626180047-add-pending-realtions.js (adds composite FKs for organization_user_invites, organization_features, etc.), and src/database/migrations/20250603104503-update-table-organization_user_invites.js (organization_code + tenant_code present in constraints).

@nevil-mathew nevil-mathew merged commit 41e992e into develop Sep 16, 2025
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants