Skip to content

Conversation

@yassinedorbozgithub
Copy link
Collaborator

@yassinedorbozgithub yassinedorbozgithub commented Aug 29, 2025

Motivation

The main motivation of this PR is to add a migration to transform the block options.typing to be a number.

Fixes #1340

Type of change:

  • Bug fix (non-breaking change which fixes an issue)

Checklist:

  • I have performed a self-review of my own code
  • New and existing unit tests pass locally with my changes

Summary by CodeRabbit

  • Chores
    • Performed a data migration to standardize the “typing” setting in blocks to a consistent numeric format, improving data consistency and stability across the app.
    • Migration runs automatically during upgrade; no user action required.

@yassinedorbozgithub yassinedorbozgithub self-assigned this Aug 29, 2025
@yassinedorbozgithub yassinedorbozgithub added the bug Something isn't working label Aug 29, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 29, 2025

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Title Check ✅ Passed The title "fix(api): add v2.3.2 options.typing migration" is concise and accurately summarizes the primary change — adding an API migration to convert Block.options.typing to a numeric type; it names the area (api) and the targeted release version so a reviewer scanning history can immediately understand the intent.
Linked Issues Check ✅ Passed The migration implements the primary coding objective from issue [#1340] by adding a migration that targets the Block collection and converts existing options.typing values to an integer via a pipeline update using $toInt, and it exposes up/down functions to run the change, so it satisfies the linked issue's requirement to normalize stored data to a numeric type. The implementation does rely on locating an admin user before running and does not show explicit handling for non-convertible values, which are relevant operational details but do not contradict the issue objective.
Out of Scope Changes Check ✅ Passed Based on the provided summary, the changes are limited to a single new migration file (api/src/migration/migrations/1756483008605-v-2-3-2.migration.ts) that implements the options.typing conversion; no unrelated files or feature changes appear in the diff, so there are no out-of-scope changes.
Description Check ✅ Passed The PR description follows the repository template by providing a Motivation with a short summary and the Fixes #1340 reference, selecting the "Bug fix" type, and noting a self-review and that tests pass locally, which covers the core information reviewers need to understand purpose and scope. Missing non-critical details include an explicit "Dependencies" entry and a clear statement or link showing whether new unit tests were added, but these omissions do not prevent understanding the change.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 1340-bug---migration-file-to-make-optionstyping-number

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
Contributor

@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: 4

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 953e28f and 9939bc2.

📒 Files selected for processing (1)
  • api/src/migration/migrations/1756483008605-v-2-2-12.migration.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
api/src/migration/migrations/1756483008605-v-2-2-12.migration.ts (5)
api/src/user/schemas/role.schema.ts (1)
  • RoleModel (57-60)
api/src/user/schemas/user.schema.ts (1)
  • UserModel (129-132)
api/src/migration/types.ts (1)
  • MigrationServices (39-43)
api/src/chat/schemas/block.schema.ts (1)
  • BlockModel (187-190)
api/src/logger/logger.service.ts (1)
  • error (32-34)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: API-Tests
🔇 Additional comments (2)
api/src/migration/migrations/1756483008605-v-2-2-12.migration.ts (2)

57-71: No action required: CommonJS export style is consistent

Matches existing migrations and the root tsconfig.json (module: "commonjs").


43-49: Harden v-2-2-12 migration conversion and update timestamps.

  • Replace $toInt: '$options.typing' with
    $convert: { input: '$options.typing', to: 'int', onError: 0, onNull: 0 }.
  • Add updatedAt: '$$NOW' in the $set.
  • Confirm legacy boolean/string mappings (default to 0 if uncertain).
  • Verify MongoDB ≥ 4.2 for pipeline updates and $convert support.

Comment on lines 20 to 30
const getAdminUser = async () => {
const RoleModel = mongoose.model<Role>(Role.name, roleSchema);
const UserModel = mongoose.model<User>(User.name, userSchema);

const adminRole = await RoleModel.findOne({ name: 'admin' });
const user = await UserModel.findOne({ roles: [adminRole!._id] }).sort({
createdAt: 'asc',
});

return user!;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

If kept, fix admin lookup: unsafe non-null and array-equality query.

  • Non-null assertion on adminRole! can throw at runtime.
  • { roles: [adminRole!._id] } matches only exact array equality, not “contains”.

Use containment and guard:

-  const adminRole = await RoleModel.findOne({ name: 'admin' });
-  const user = await UserModel.findOne({ roles: [adminRole!._id] }).sort({
-    createdAt: 'asc',
-  });
-
-  return user!;
+  const adminRole = await RoleModel.findOne({ name: 'admin' }).lean();
+  if (!adminRole) return null;
+  return UserModel.findOne({ roles: { $in: [adminRole._id] } })
+    .sort({ createdAt: 1 })
+    .lean();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const getAdminUser = async () => {
const RoleModel = mongoose.model<Role>(Role.name, roleSchema);
const UserModel = mongoose.model<User>(User.name, userSchema);
const adminRole = await RoleModel.findOne({ name: 'admin' });
const user = await UserModel.findOne({ roles: [adminRole!._id] }).sort({
createdAt: 'asc',
});
return user!;
};
const getAdminUser = async () => {
const RoleModel = mongoose.model<Role>(Role.name, roleSchema);
const UserModel = mongoose.model<User>(User.name, userSchema);
const adminRole = await RoleModel.findOne({ name: 'admin' }).lean();
if (!adminRole) return null;
return UserModel.findOne({ roles: { $in: [adminRole._id] } })
.sort({ createdAt: 1 })
.lean();
};
🤖 Prompt for AI Agents
In api/src/migration/migrations/1756483008605-v-2-2-12.migration.ts around lines
20-30, the code unsafely uses a non-null assertion on adminRole and queries
roles with an array equality which only matches exact arrays; instead, guard
against a missing adminRole (e.g., if (!adminRole) throw new Error('admin role
not found') or return null) and change the user lookup to use a containment
match such as await UserModel.findOne({ roles: adminRole._id }).sort({
createdAt: 'asc' }) or explicitly { roles: { $in: [adminRole._id] } }) so you
remove the non-null assertion and correctly find users that have the admin role.

};

const migrateBlockOptionsTyping = async (services: MigrationServices) => {
const BlockModel = mongoose.model<Block>(Block.name, blockSchema).collection;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Prevent Mongoose OverwriteModelError by reusing compiled models.

Repeated mongoose.model(name, schema) may recompile an existing model.

-  const BlockModel = mongoose.model<Block>(Block.name, blockSchema).collection;
+  const BlockModel =
+    (mongoose.models[Block.name] as mongoose.Model<Block> | undefined)?.collection ??
+    mongoose.model<Block>(Block.name, blockSchema).collection;

If you keep getAdminUser, apply the same pattern for Role/User.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const BlockModel = mongoose.model<Block>(Block.name, blockSchema).collection;
const BlockModel =
(mongoose.models[Block.name] as mongoose.Model<Block> | undefined)?.collection ??
mongoose.model<Block>(Block.name, blockSchema).collection;
🤖 Prompt for AI Agents
In api/src/migration/migrations/1756483008605-v-2-2-12.migration.ts around line
33, calling mongoose.model<Block>(Block.name, blockSchema).collection will throw
OverwriteModelError if the model is already compiled; change to reuse existing
compiled models by checking mongoose.models[Block.name] and using
mongoose.model(Block.name) if present, otherwise compile with the schema, and
then access .collection; apply the same guard/pattern for Role and User (and any
getAdminUser helper) so you never call mongoose.model(name, schema)
unconditionally for a model that may already exist.

Comment on lines 35 to 41
const adminUser = await getAdminUser();

if (!adminUser) {
services.logger.warn('Unable to process block, no admin user found');
return;
}

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove unnecessary admin-user gating; it can silently skip the migration.

This migration doesn’t depend on a user context. If no admin exists, all updates are skipped, leaving data invalid.

Apply within-range removal:

-  const adminUser = await getAdminUser();
-
-  if (!adminUser) {
-    services.logger.warn('Unable to process block, no admin user found');
-    return;
-  }

Also delete the now-unused helper and imports:

- import roleSchema, { Role } from '@/user/schemas/role.schema';
- import userSchema, { User } from '@/user/schemas/user.schema';
@@
-/**
- * @returns The admin user or null
- */
-const getAdminUser = async () => {
-  const RoleModel = mongoose.model<Role>(Role.name, roleSchema);
-  const UserModel = mongoose.model<User>(User.name, userSchema);
-
-  const adminRole = await RoleModel.findOne({ name: 'admin' });
-  const user = await UserModel.findOne({ roles: [adminRole!._id] }).sort({
-    createdAt: 'asc',
-  });
-
-  return user!;
-};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const adminUser = await getAdminUser();
if (!adminUser) {
services.logger.warn('Unable to process block, no admin user found');
return;
}
🤖 Prompt for AI Agents
In api/src/migration/migrations/1756483008605-v-2-2-12.migration.ts around lines
35 to 41, remove the admin-user gating that returns early if getAdminUser() is
falsy so the migration always runs; delete the getAdminUser() call and the
surrounding if-block, apply any necessary in-range edits so updates proceed
regardless of admin existence, and also remove the now-unused helper function
and its imports from this file and any related index exports to avoid dead
code/import errors.

Comment on lines 50 to 54
} catch (error) {
services.logger.error(`Failed to update typing : ${error.message}`);

throw error instanceof Error ? error : new Error(error);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Type-safe error handling; avoid unknown/stringly errors and preserve stack.

Accessing error.message without narrowing breaks under useUnknownInCatchVariables; new Error(error) with non-string is unsafe.

-  } catch (error) {
-    services.logger.error(`Failed to update typing : ${error.message}`);
-
-    throw error instanceof Error ? error : new Error(error);
-  }
+  } catch (error: unknown) {
+    const err = error instanceof Error ? error : new Error(String(error));
+    services.logger.error('Failed to update typing', err);
+    throw err;
+  }
@@
-    } catch (error) {
-      services.logger.error(`Migration failed : ${error.message}`);
-      throw error instanceof Error ? error : new Error(error);
-    }
+    } catch (error: unknown) {
+      const err = error instanceof Error ? error : new Error(String(error));
+      services.logger.error('Migration failed', err);
+      throw err;
+    }

Also applies to: 63-66

🤖 Prompt for AI Agents
In api/src/migration/migrations/1756483008605-v-2-2-12.migration.ts around lines
50-54 and similarly 63-66, the catch blocks assume `error` has a `.message` and
do `new Error(error)` which is unsafe under `useUnknownInCatchVariables` and can
drop stack info; update both catches to treat the caught value as `unknown`,
narrow with `if (error instanceof Error)` to log `error.message` (or
`error.stack` for richer context) and rethrow the original Error to preserve
stack, otherwise convert the unknown to a string via `String(error)` for logging
and throw `new Error(String(error))`.

}

try {
await BlockModel.updateMany({ 'options.typing': { $exists: true } }, [
Copy link
Contributor

Choose a reason for hiding this comment

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

Quick question: what if a single documents contains an invalid options.typing field and thus cannot be parsed to int ?

Copy link
Collaborator Author

@yassinedorbozgithub yassinedorbozgithub Sep 17, 2025

Choose a reason for hiding this comment

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

This is a good edge case, but we are not supposed to face this case except if the data is updated not from the UI.
This migration main motivation is to resolve block.options.typing values previously set it accidentally with a string values (UI is fixed to not introduce new values with not expected type).

@yassinedorbozgithub yassinedorbozgithub changed the title fix(api): add v2.2.12 options.typing migration fix(api): add v2.3.2 options.typing migration Sep 16, 2025
Copy link
Contributor

@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: 1

🧹 Nitpick comments (4)
api/src/migration/migrations/1756483008605-v-2-3-2.migration.ts (4)

50-54: Harden error logging and rethrow.

Avoid accessing error.message on unknown; log the error object and ensure the rethrow handles non‑Error values.

-  } catch (error) {
-    services.logger.error(`Failed to update typing : ${error.message}`);
-
-    throw error instanceof Error ? error : new Error(error);
-  }
+  } catch (err) {
+    services.logger.error('Failed to update typing', err);
+    throw err instanceof Error ? err : new Error(String(err));
+  }

63-66: Same logging/throwing issue in up().

-    } catch (error) {
-      services.logger.error(`Migration failed : ${error.message}`);
-      throw error instanceof Error ? error : new Error(error);
+    } catch (err) {
+      services.logger.error('Migration failed', err);
+      throw err instanceof Error ? err : new Error(String(err));
     }

42-49: Optional: emit counters for visibility.

Log matched/modified counts from updateMany to know the migration effect.

If your LoggerService supports info/log, add after update:

+    // Optional: visibility
+    // services.logger.info?.(`Block options.typing normalized: matched=${res.matchedCount}, modified=${res.modifiedCount}`);

68-70: Down is a no‑op—confirm this is acceptable.

Data type changes aren’t reversible here. If that’s expected, consider adding a comment explaining why.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9939bc2 and 2835147.

📒 Files selected for processing (1)
  • api/src/migration/migrations/1756483008605-v-2-3-2.migration.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
api/src/migration/migrations/1756483008605-v-2-3-2.migration.ts (5)
api/src/user/schemas/role.schema.ts (1)
  • RoleModel (57-60)
api/src/user/schemas/user.schema.ts (1)
  • UserModel (129-132)
api/src/migration/types.ts (1)
  • MigrationServices (39-43)
api/src/chat/schemas/block.schema.ts (1)
  • BlockModel (187-190)
api/src/logger/logger.service.ts (1)
  • error (32-34)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: API-Tests
  • GitHub Check: Frontend Tests
🔇 Additional comments (3)
api/src/migration/migrations/1756483008605-v-2-3-2.migration.ts (3)

42-49: Confirm MongoDB version supports pipeline updates.

collection.updateMany([...pipeline]) requires MongoDB 4.2+. Ensure target environments meet this.


57-71: Confirm migration export style matches your migration runner (CommonJS vs ESM).

Verified: the migration file api/src/migration/migrations/1756483008605-v-2-3-2.migration.ts (and other files in api/src/migration/migrations — 1735836154221-v-2-2-0, 1748492346868-v-2-2-9, 1754055187170-v-2-2-11) use ESM-style imports but export via CommonJS module.exports. Ensure your migration runner supports CommonJS exports with TypeScript/ESM imports; if the runner requires ESM, convert these migrations to use export default (or adjust the runner).


42-49: Make conversion tolerant: use $convert with onError/onNull and exclude nulls

$t oInt can throw on unconvertible values and abort the update; use $convert and tighten the filter to skip nulls.

File: api/src/migration/migrations/1756483008605-v-2-3-2.migration.ts (lines 42–49)

-    await BlockModel.updateMany({ 'options.typing': { $exists: true } }, [
-      {
-        $set: {
-          'options.typing': { $toInt: '$options.typing' },
-        },
-      },
-    ]);
+    await BlockModel.updateMany(
+      { 'options.typing': { $exists: true, $ne: null } },
+      [
+        {
+          $set: {
+            'options.typing': {
+              $convert: {
+                input: '$options.typing',
+                to: 'int',
+                onError: 0,
+                onNull: 0,
+              },
+            },
+          },
+        },
+      ]
+    );

Confirm desired default when conversion fails (0 used above).

Comment on lines +12 to +13
import roleSchema, { Role } from '@/user/schemas/role.schema';
import userSchema, { User } from '@/user/schemas/user.schema';
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove unused admin lookup and hard dependency (migration can silently no‑op).

The admin user is fetched but never used. Worse, the migration returns early if no admin exists, leaving data un-migrated. Drop this code to ensure the migration always runs.

-import roleSchema, { Role } from '@/user/schemas/role.schema';
-import userSchema, { User } from '@/user/schemas/user.schema';
+// Admin lookup removed: not needed for this migration

-/**
- * @returns The admin user or null
- */
-const getAdminUser = async () => {
-  const RoleModel = mongoose.model<Role>(Role.name, roleSchema);
-  const UserModel = mongoose.model<User>(User.name, userSchema);
-
-  const adminRole = await RoleModel.findOne({ name: 'admin' });
-  const user = await UserModel.findOne({ roles: [adminRole!._id] }).sort({
-    createdAt: 'asc',
-  });
-
-  return user!;
-};
-
-  const adminUser = await getAdminUser();
-
-  if (!adminUser) {
-    services.logger.warn('Unable to process block, no admin user found');
-    return;
-  }

Also applies to: 17-31, 35-41

🤖 Prompt for AI Agents
In api/src/migration/migrations/1756483008605-v-2-3-2.migration.ts around lines
12-13 (and affecting 17-31, 35-41), remove the unused admin lookup and the
early-return that makes the migration a hard no-op when no admin exists; delete
the code that queries for the admin user and any conditional that returns if
admin is falsy so the migration logic always runs, and remove now-unused imports
(roleSchema/Role and userSchema/User) or adjust imports/typing accordingly;
ensure the remaining migration code executes unconditionally and still handles
the case where no admin-related data exists without throwing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working needs-review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🐛 [BUG] - migration file to make options.typing number

3 participants