Skip to content

Upgrade mongoose version to 9.5.*#789

Draft
Luquitasjeffrey wants to merge 3 commits intomainfrom
update-mongoose-to-9
Draft

Upgrade mongoose version to 9.5.*#789
Luquitasjeffrey wants to merge 3 commits intomainfrom
update-mongoose-to-9

Conversation

@Luquitasjeffrey
Copy link
Copy Markdown
Collaborator

@Luquitasjeffrey Luquitasjeffrey commented Apr 26, 2026

Summary by CodeRabbit

  • Bug Fixes

    • Standardized ID handling throughout the application to resolve potential inconsistencies in order, community, and dispute lookups.
  • Chores

    • Upgraded Mongoose to v9.5.0.
    • Updated TypeScript configuration for improved compilation performance.
    • Refined type definitions for database models.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 26, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 867a4911-392d-4593-a5c8-e025180df616

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

This pull request systematically updates MongoDB ObjectId handling across the codebase by converting _id values to strings for persistence and database queries, updating TypeScript model interfaces to type _id as mongoose.Types.ObjectId, upgrading Mongoose from 8.17.1 to 9.5.0, and ensuring consistent string-based ID usage in message payloads and query filters.

Changes

Cohort / File(s) Summary
Core Command & Message Handling
bot/commands.ts, bot/messages.ts
Updated MongoDB queries and message payloads to use .toString() on _id values for order/user lookups, standardized identifier persistence (e.g., canceled_by, order_id fields), and adjusted I18n initialization parameters.
Order Module Files
bot/modules/orders/commands.ts, bot/modules/orders/messages.ts, bot/modules/orders/scenes.ts, bot/modules/orders/takeOrder.ts, bot/ordersActions.ts
Converted creator_id, seller_id, and buyer_id fields to string values via .toString(), updated order-count queries and range-order payload generation to use stringified _id values.
Community Module Files
bot/modules/community/commands.ts, bot/modules/community/messages.ts, bot/modules/community/scenes.ts
Refactored getOrderCountByCommunity return type and updated community lookups to use _id.toString() for ID matching; standardized creator/solver ID persistence as strings; added numeric conversion for PAYMENT_ATTEMPTS environment variable.
Dispute Module Files
bot/modules/dispute/actions.ts, bot/modules/dispute/commands.ts
Updated Dispute document queries and field assignments to consistently use .toString() for buyer_id, seller_id, order_id, and solver_id fields; adjusted authorization checks against stringified user IDs.
Model Type Definitions
models/order.ts, models/user.ts, models/community.ts
Changed _id field types from string to mongoose.Types.ObjectId; updated ICommunity nested subdocuments to plain arrays instead of DocumentArray<...> and removed Document inheritance from IOrderChannel and IUsernameId.
Database & Configuration
db_connect.ts, package.json, tsconfig.json
Removed deprecated Mongoose connection options (useNewUrlParser, useUnifiedTopology); upgraded Mongoose from ^8.17.1 to ^9.5.0; enabled skipLibCheck in TypeScript compiler options.
Validation & Job Processing
bot/validations.ts, bot/start.ts, jobs/pending_payments.ts
Updated Mongo query filters to cast/convert IDs to ObjectId or string as appropriate; added getPaymentAttempts() helper to centralize environment-based configuration; replaced FilterQuery typed objects with any for query predicates.
Utility & Supporting Modules
bot/modules/block/commands.ts, bot/modules/nostr/events.ts, bot/scenes.ts, util/communityHelper.ts, util/index.ts
Updated order-existence and ID-equality checks to use .toString() for user/order _id comparisons; standardized Nostr event tag generation and pending-payment query filters to use stringified IDs.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • knocte
  • grunch
  • mostronatorcoder

Poem

🐰 bounces with glee
ObjectIds converted with care,
.toString() hops everywhere!
Mongoose grows fresh and bright,
IDs stringified just right—
Our bot garden shines with might! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Upgrade mongoose version to 9.5.*' is partially related to the changeset. While a mongoose version upgrade is included (package.json), the bulk of changes involve systematic ID handling refactoring (converting ObjectId to strings throughout the codebase), which is the more substantial part of the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch update-mongoose-to-9

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.

@Luquitasjeffrey Luquitasjeffrey marked this pull request as draft April 26, 2026 01:36
Copy link
Copy Markdown
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: 9

Caution

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

⚠️ Outside diff range comments (4)
bot/validations.ts (1)

523-548: ⚠️ Potential issue | 🔴 Critical

Inconsistent ID handling — seller_id/buyer_id queries pass ObjectId against String schema fields without .toString().

In validateReleaseOrder (lines 524, 536) and validateUserWaitingOrder (lines 702, 707), the filters use user._id directly:

seller_id: user._id,        // user._id is ObjectId
buyer_id:  user._id,        // schema defines these as { type: String }

But models/order.ts defines both seller_id and buyer_id as { type: String }. In contrast, other validators in this same file (validateDisputeOrder line 574, validateFiatSentOrder line 600, validateSeller line 635) correctly call .toString().

Mongoose 9 does not auto-cast ObjectId values to strings when querying String-typed schema fields. These four callsites will result in no matches — findOne and find will silently return null or [], breaking the validateReleaseOrder release flow and validateUserWaitingOrder concurrent-order guard, both critical user-facing paths.

Proposed fix
-    let where: any = {
-      seller_id: user._id,
+    let where: any = {
+      seller_id: user._id.toString(),
       status: 'WAITING_BUYER_INVOICE',
       _id: new ObjectId(orderId),
     };
@@
     where = {
       $and: [
-        { seller_id: user._id },
+        { seller_id: user._id.toString() },
         {
           $or: [
             { status: 'ACTIVE' },
             { status: 'FIAT_SENT' },
             { status: 'DISPUTE' },
           ],
         },
       ],
     };
@@
-    let where: any = {
-      seller_id: user._id,
+    let where: any = {
+      seller_id: user._id.toString(),
       status: 'WAITING_PAYMENT',
     };
@@
-    where = {
-      buyer_id: user._id,
+    where = {
+      buyer_id: user._id.toString(),
       status: 'WAITING_BUYER_INVOICE',
     };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bot/validations.ts` around lines 523 - 548, The queries in
validateReleaseOrder and validateUserWaitingOrder (where you set seller_id:
user._id and buyer_id: user._id) pass an ObjectId into schema fields defined as
String, causing no matches; update those query assignments to use
user._id.toString() (e.g., set seller_id and buyer_id to user._id.toString()) so
they match the String-typed fields, leaving the ObjectId(orderId) usage for _id
unchanged and consistent with other validators like validateDisputeOrder and
validateFiatSentOrder.
bot/scenes.ts (1)

147-171: ⚠️ Potential issue | 🟡 Minor

Inconsistent ID handling between query and insert in the same scene.

The PendingPayment.findOne filter at line 148 stringifies order._id, but the new PendingPayment({...}) call at lines 162–169 still stores user_id: buyer._id and order_id: order._id as raw ObjectId values. Going forward, the lookup and the persist path should agree, otherwise this duplicate‑payment guard can drift if the schema field types are tightened, and it diverges from the convention now applied across bot/ordersActions.ts, bot/modules/orders/takeOrder.ts, and the dispute/block flows.

🐛 Suggested fix
         const pp = new PendingPayment({
           amount: order.amount,
           payment_request: lnInvoice,
-          user_id: buyer._id,
+          user_id: buyer._id.toString(),
           description: order.description,
           hash: order.hash,
-          order_id: order._id,
+          order_id: order._id.toString(),
         });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bot/scenes.ts` around lines 147 - 171, The lookup uses a stringified order id
(PendingPayment.findOne with order._id.toString()) but the insert constructs
PendingPayment with raw ObjectId values (new PendingPayment using user_id:
buyer._id and order_id: order._id), causing inconsistent ID types; update the
creation path in the block that constructs the PendingPayment (the new
PendingPayment instantiation and saved order.paid_hold_buyer_invoice_updated
logic) to store the same string form used in the query (e.g., use
order._id.toString() and buyer._id.toString() for order_id and user_id) so
lookup and persist agree with PendingPayment.findOne.
bot/messages.ts (1)

1143-1143: ⚠️ Potential issue | 🟡 Minor

Missed .toString() for orderId here — inconsistent with the rest of the PR.

Other call sites of the setinvoice_cmd_order template were normalized to order._id.toString() (e.g., Line 294), but notLightningInvoiceMessage still passes the raw order._id. Behaviorally the i18n engine will stringify it, but please make this consistent so the model-level _id: Types.ObjectId typing is respected uniformly.

🔧 Proposed fix
-      ctx.i18n.t('setinvoice_cmd_order', { orderId: order._id }),
+      ctx.i18n.t('setinvoice_cmd_order', { orderId: order._id.toString() }),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bot/messages.ts` at line 1143, The i18n call in notLightningInvoiceMessage
passes order._id directly which breaks consistency with other uses; update the
ctx.i18n.t invocation that references 'setinvoice_cmd_order' to pass
order._id.toString() instead of order._id so the model-level Types.ObjectId is
normalized (look for the ctx.i18n.t('setinvoice_cmd_order', { orderId: order._id
}) call in notLightningInvoiceMessage and replace the orderId value with
order._id.toString()).
bot/start.ts (1)

103-140: ⚠️ Potential issue | 🔴 Critical

Use QueryFilter<IOrder> instead of any — Mongoose 9.5 broke with FilterQuery.

These filter objects currently lose type safety with any. With Mongoose 9.5.0 (in package.json), use mongoose.QueryFilter<IOrder> to restore schema-aware typing. Note that FilterQuery<T> was removed in Mongoose 9.0; the new public type is QueryFilter<T>, which properly supports $and/$or operators. This catch typos in field names at compile time.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bot/start.ts` around lines 103 - 140, Replace the loose-typed "where: any"
filter objects with Mongoose's schema-aware type by declaring them as
QueryFilter<IOrder> (instead of any) wherever you build query filters in this
handler (e.g., the "where" variables used before Order.find(...) in the branches
for the default, '/fiatsent', '/release', and '/setinvoice' commands); import
QueryFilter from mongoose and the IOrder interface/type from your
models/typings, update each "where" declaration to use QueryFilter<IOrder>, and
ensure the typed filters still compile with the $and/$or/$in operators used so
field-name typos are caught at compile time.
🧹 Nitpick comments (9)
db_connect.ts (1)

18-18: Drop the empty options object and the unnecessary cast.

Passing {} as ConnectOptions is a no-op and the cast hides a real type problem if one ever arises. With the deprecated useNewUrlParser / useUnifiedTopology removed, just call mongoose.connect(MONGO_URI) and remove the now-unused ConnectOptions import.

Proposed diff
-import mongoose, { ConnectOptions } from 'mongoose';
+import mongoose from 'mongoose';
@@
 const connect = () => {
-  mongoose.connect(MONGO_URI, {} as ConnectOptions);
+  mongoose.connect(MONGO_URI);
   return mongoose;
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@db_connect.ts` at line 18, Remove the unnecessary cast and empty options when
connecting to MongoDB: replace the call to mongoose.connect(MONGO_URI, {} as
ConnectOptions) with mongoose.connect(MONGO_URI) and delete the now-unused
ConnectOptions import; ensure you update any imports or references so only
MONGO_URI and mongoose are used in db_connect.ts.
bot/validations.ts (3)

25-25: Drop the leftover migration breadcrumb.

// Removed redundant ObjectId declaration documents a transient cleanup, not the code. Please remove it before merge.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bot/validations.ts` at line 25, Remove the stray migration breadcrumb comment
"// Removed redundant ObjectId declaration" from bot/validations.ts; locate that
exact comment string in the file and delete it so only actual code and
meaningful comments remain before merging.

605-607: Inconsistent _id filter construction.

Other branches in this file build the _id filter as new ObjectId(orderId) (lines 526, 548, 572). Here it's a raw string, which works thanks to Mongoose's path casting but is a stylistic outlier and easy to miss when refactoring.

-    if (orderId) {
-      where._id = orderId;
-    }
+    if (orderId) {
+      where._id = new ObjectId(orderId);
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bot/validations.ts` around lines 605 - 607, The _id filter is being set as a
raw string in the conditional (where._id = orderId) which is inconsistent with
other branches that use new ObjectId(orderId); update that assignment to
construct an ObjectId (i.e., where._id = new ObjectId(orderId)) using the same
ObjectId constructor used in the other branches (see the usages around lines
building _id with new ObjectId) so the filter style matches and avoid refactor
hazards.

523-523: Regression in query typing: FilterQuery<OrderQuery>any.

The where objects were previously typed (per the AI summary) and are now any. That weakens type safety exactly where it matters most for this PR — the Mongoose 9 cast/typing changes are the kind of thing those types would have caught (e.g. the seller_id: user._id issue flagged above). Consider keeping the typed form, e.g. FilterQuery<IOrder>, even if you have to add a small union/intersection to allow the $and/$or shape.

Also applies to: 598-598, 701-701

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bot/validations.ts` at line 523, The local query variable `where` was
loosened to `any`, regressing type-safety; change its type back to a Mongoose
FilterQuery for orders and allow the $and/$or compound shapes—e.g. replace `let
where: any = {` with something like `let where: FilterQuery<IOrder> & ({ $and?:
FilterQuery<IOrder>[]; $or?: FilterQuery<IOrder>[] }) = {` (and apply the same
typed change at the other occurrences flagged around the file), so you retain
strict typing for fields while permitting compound operators.
bot/modules/orders/scenes.ts (1)

78-78: Optional chaining reads cleaner here.

community && community._id.toString() will pass through null/undefined/false. Optional chaining is more idiomatic for ID derivation and yields undefined consistently when community is missing.

♻️ Suggested refactor
-        community_id: community && community._id.toString(),
+        community_id: community?._id.toString(),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bot/modules/orders/scenes.ts` at line 78, Replace the brittle "community &&
community._id.toString()" expression with optional chaining to consistently
yield undefined when community is missing; update the property to use
"community?._id?.toString()" (reference: the community_id assignment in the
object where community is referenced).
jobs/pending_payments.ts (1)

13-15: Export getPaymentAttempts to eliminate the same logic duplicated across 3+ files.

The same Number(process.env.PAYMENT_ATTEMPTS) pattern exists in:

  • bot/start.ts:1018-1021
  • bot/modules/community/messages.ts:161
  • bot/modules/community/scenes.ts:949

Hoist this helper (e.g., into util/index.ts) and reuse it everywhere so the default and coercion behavior stay consistent.

Also note: Number(env) || 2 will return 2 when PAYMENT_ATTEMPTS="0". That edge case is benign here, but consider explicit Number.isFinite(...) ? n : 2 if you want the fallback to apply only when the env var is unset/invalid.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jobs/pending_payments.ts` around lines 13 - 15, Export the getPaymentAttempts
helper so the env-parsing logic is centralized and reused instead of duplicated;
move or re-create getPaymentAttempts in util/index.ts, export it, and replace
the three ad-hoc uses (getPaymentAttempts in jobs/pending_payments.ts plus the
calls in bot/start.ts, bot/modules/community/messages.ts, and
bot/modules/community/scenes.ts) to import and call the shared function; update
the implementation to use an explicit check like const n =
Number(process.env.PAYMENT_ATTEMPTS); return Number.isFinite(n) ? n : 2 so
PAYMENT_ATTEMPTS="0" yields 0 while invalid/absent values fall back to 2.
bot/messages.ts (3)

752-757: Duplicated community-i18n construction; consider extracting a helper.

Lines 752-757 and 805-810 are identical and run on every published order, paying the cost of new I18n({ directory: 'locales' }) (synchronous file load) each time. Extract a small helper (e.g., getCommunityI18nContext(language)) and ideally cache I18n instances per language to avoid reloading locale files on every publish.

♻️ Sketch
const communityI18nCache = new Map<string, I18nContext>();
const getCommunityI18nContext = (language: string): I18nContext => {
  let ctx = communityI18nCache.get(language);
  if (!ctx) {
    ctx = new I18n({
      defaultLanguageOnMissing: true,
      defaultLanguage: language,
      directory: 'locales',
    }).createContext(language, {});
    communityI18nCache.set(language, ctx);
  }
  return ctx;
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bot/messages.ts` around lines 752 - 757, The duplicated construction of
communityI18n (new I18n({...}).createContext(...)) should be extracted into a
helper like getCommunityI18nContext(language) that caches I18n contexts per
language (e.g., a Map<string, I18nContext>) so you reuse the same context
instead of reloading locale files on every publish; replace both occurrences
that set communityI18n with calls to getCommunityI18nContext(community.language)
and ensure the helper initializes the I18n with defaultLanguageOnMissing,
defaultLanguage and directory exactly as currently used.

749-757: Remove the redundant new ObjectId(...) cast and the ! assertion on order.community_id in both publishBuy and publishSell functions.

order.community_id is already narrowed to truthy by the if (order.community_id) check, and Mongoose automatically casts string or Types.ObjectId values for the _id filter, so wrapping in new ObjectId(...) is unnecessary. The same duplicate pattern appears at lines 802–810.

Additionally, defaultLanguage and the second argument to createContext both receive community.language—pass the language once:

♻️ Proposed fix
-      const community = await Community.findOne({ _id: new ObjectId(order.community_id!) });
+      const community = await Community.findOne({ _id: order.community_id });
       if (community && community.language) {
         communityI18n = new I18n({
           defaultLanguageOnMissing: true,
           defaultLanguage: community.language,
           directory: 'locales',
-        }).createContext(community.language, {});
+        }).createContext({});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bot/messages.ts` around lines 749 - 757, Remove the unnecessary ObjectId cast
and non-null assertion and avoid passing the community language twice: in both
publishBuy and publishSell replace Community.findOne({ _id: new
ObjectId(order.community_id!) }) with Community.findOne({ _id:
order.community_id }) (no casting or "!"), and when constructing communityI18n
keep defaultLanguage: community.language but call createContext with only the
language argument (e.g., .createContext(community.language)) so the language
value isn't duplicated.

1207-1995: Standardize order._id to .toString() when passing to i18n.t() for consistency.

The file mixes stringified and unstringified usage (22 instances without .toString() vs. 8 with it), despite order._id being typed as mongoose.Types.ObjectId. While runtime behavior is unchanged—i18n auto-stringifies—standardizing across all 22+ call sites would improve code consistency and clarity. This pattern extends throughout the bot/ directory (25 unstringified vs. 9 stringified occurrences), suggesting a broader refactoring opportunity.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bot/messages.ts` around lines 1207 - 1995, Multiple places in this file pass
order._id into i18n.t() without explicitly stringifying it; standardize all such
usages to use order._id.toString() for consistency. Search for functions that
interpolate order._id (e.g., successCancelOrderMessage,
counterPartyCancelOrderMessage, okCooperativeCancelMessage,
wizardAddInvoiceExitMessage, expiredOrderMessage,
toBuyerPendingPaymentFailedMessage, toAdminChannelPendingPaymentSuccessMessage,
toAdminChannelPendingPaymentFailedMessage and any other helpers in this file)
and replace the argument passed to i18n.t() from order._id to
order._id.toString() everywhere it appears. Ensure you only change the
interpolation value (keep surrounding i18n keys and options intact) and run
tests/lint to confirm no type issues.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@bot/commands.ts`:
- Line 751: The call to setCooperativeCancelFlag currently wraps order._id with
new ObjectId(...) and casts to any; remove the unsafe cast and pass a string
instead (e.g., use order._id.toString() or pass order._id directly if already a
string) so the call becomes setCooperativeCancelFlag(order._id.toString(),
initiator); update callers to stop using "as any" and, optionally, remove the
redundant new ObjectId(orderId) conversion inside setCooperativeCancelFlag if
you want to rely on Mongoose auto-casting.
- Around line 1-3: Move the in-module declaration so all imports come first:
place "const { ObjectId } = mongoose.Types;" after the import statements (below
the imports of mongoose and the named validators like validateFiatSentOrder and
validateReleaseOrder) so that ObjectId is still derived from mongoose but no
longer sits between import lines, resolving the import/first ESLint error.
- Line 685: The call currently constructs and casts an ObjectId around orderId
which bypasses ordersActions.getOrder's own validation; update both occurrences
(the call at the order retrieval near the earlier block and the second similar
call) to pass the raw string orderId directly to ordersActions.getOrder so its
ObjectId.isValid(orderId) check and user-facing notValidIdMessage remain
effective (remove new ObjectId(orderId) as any and pass orderId: string to
getOrder).
- Line 142: Remove the unnecessary new ObjectId(...) wrappings around
string-typed ID fields (e.g. places calling Order.findOne({ _id: new
ObjectId(orderId) }) and User.findOne({ _id: new ObjectId(order.buyer_id!) }))
because Mongoose 9 auto-casts strings; instead pass the raw string IDs to
findOne. Replace occurrences that wrap order.buyer_id, order.seller_id, orderId,
etc., with the plain values and add explicit null/undefined guards where the
field is typed string | null (for example, if (!order.buyer_id) return; const
buyer = await User.findOne({ _id: order.buyer_id });). Update all listed call
sites in bot/commands.ts (and similar patterns in bot/validations.ts,
jobs/pending_payments.ts, bot/messages.ts) to remove new ObjectId and drop
non-null assertions so runtime nulls are handled explicitly.

In `@bot/modules/community/messages.ts`:
- Around line 159-163: The guard using Number(process.env.PAYMENT_ATTEMPTS) can
become NaN and bypass the duplicate-payment check; replace that ad-hoc
conversion by importing and using the shared getPaymentAttempts() helper from
jobs/pending_payments.ts (export it if not already exported) and use it in the
PendingPayment.findOne query (the same change should be applied to
bot/modules/community/scenes.ts around the withdrawal logic and bot/start.ts
where PAYMENT_ATTEMPTS is used) so attempts uses a validated numeric fallback
(e.g., || 2) instead of Number(process.env.PAYMENT_ATTEMPTS).

In `@bot/scenes.ts`:
- Line 149: The filter uses Number(process.env.PAYMENT_ATTEMPTS) which can
become NaN and make the $lt check always fail; define and validate a
PAYMENT_ATTEMPTS constant at startup (like the existing
HOLD_INVOICE_EXPIRATION_WINDOW pattern) by parsing process.env.PAYMENT_ATTEMPTS
(e.g., parseInt) and throwing/failing fast if it is missing or NaN, then replace
Number(process.env.PAYMENT_ATTEMPTS) in the attempts: { $lt: ... } expression
with that validated PAYMENT_ATTEMPTS constant so the duplicate‑payment guard
works reliably.

In `@bot/start.ts`:
- Around line 6-7: Remove the unused destructure "ObjectId" (and the "mongoose"
import if it's only present to obtain ObjectId) from this module: locate the
import line that reads "import mongoose from 'mongoose'; const { ObjectId } =
mongoose.Types;" and delete the ObjectId destructure (and the whole mongoose
import if no other symbol from mongoose is referenced elsewhere in the file);
ensure any existing code that uses _id.toString() or string comparisons
continues to work without ObjectId, adjust imports/types if necessary, and run
ESLint to confirm the warning is resolved.

In `@jobs/pending_payments.ts`:
- Line 28: Remove unnecessary use of new ObjectId(...) when querying (e.g.,
Order.findOne({ _id: new ObjectId(pending.order_id) }) and other similar
queries) and instead pass the string directly so Mongoose can auto-cast;
additionally, replace non-null assertions on nullable fields buyer_id and
seller_id with explicit null checks and proper error handling before querying
(e.g., verify order.buyer_id and order.seller_id are present and throw or return
a clear error if missing), then call User.findOne({ _id: order.buyer_id }) /
User.findOne({ _id: order.seller_id }) and handle the case where the returned
buyerUser or sellerUser is null.

In `@package.json`:
- Line 31: package.json currently depends on "mongoose": "^9.5.0" which requires
Node.js >=20.19.0 but the package.json "engines.node" declares >=18.0.0; update
the "engines.node" constraint to ">=20.19.0" and align your CI workflow (node
version in CI config) to use >=20.19.0, then run/author regression tests around
code paths using mongoose (and review mongoose 9.x migration notes) to catch
breaking changes before merging; look for the "mongoose" dependency and the
"engines.node" field to apply these changes.

---

Outside diff comments:
In `@bot/messages.ts`:
- Line 1143: The i18n call in notLightningInvoiceMessage passes order._id
directly which breaks consistency with other uses; update the ctx.i18n.t
invocation that references 'setinvoice_cmd_order' to pass order._id.toString()
instead of order._id so the model-level Types.ObjectId is normalized (look for
the ctx.i18n.t('setinvoice_cmd_order', { orderId: order._id }) call in
notLightningInvoiceMessage and replace the orderId value with
order._id.toString()).

In `@bot/scenes.ts`:
- Around line 147-171: The lookup uses a stringified order id
(PendingPayment.findOne with order._id.toString()) but the insert constructs
PendingPayment with raw ObjectId values (new PendingPayment using user_id:
buyer._id and order_id: order._id), causing inconsistent ID types; update the
creation path in the block that constructs the PendingPayment (the new
PendingPayment instantiation and saved order.paid_hold_buyer_invoice_updated
logic) to store the same string form used in the query (e.g., use
order._id.toString() and buyer._id.toString() for order_id and user_id) so
lookup and persist agree with PendingPayment.findOne.

In `@bot/start.ts`:
- Around line 103-140: Replace the loose-typed "where: any" filter objects with
Mongoose's schema-aware type by declaring them as QueryFilter<IOrder> (instead
of any) wherever you build query filters in this handler (e.g., the "where"
variables used before Order.find(...) in the branches for the default,
'/fiatsent', '/release', and '/setinvoice' commands); import QueryFilter from
mongoose and the IOrder interface/type from your models/typings, update each
"where" declaration to use QueryFilter<IOrder>, and ensure the typed filters
still compile with the $and/$or/$in operators used so field-name typos are
caught at compile time.

In `@bot/validations.ts`:
- Around line 523-548: The queries in validateReleaseOrder and
validateUserWaitingOrder (where you set seller_id: user._id and buyer_id:
user._id) pass an ObjectId into schema fields defined as String, causing no
matches; update those query assignments to use user._id.toString() (e.g., set
seller_id and buyer_id to user._id.toString()) so they match the String-typed
fields, leaving the ObjectId(orderId) usage for _id unchanged and consistent
with other validators like validateDisputeOrder and validateFiatSentOrder.

---

Nitpick comments:
In `@bot/messages.ts`:
- Around line 752-757: The duplicated construction of communityI18n (new
I18n({...}).createContext(...)) should be extracted into a helper like
getCommunityI18nContext(language) that caches I18n contexts per language (e.g.,
a Map<string, I18nContext>) so you reuse the same context instead of reloading
locale files on every publish; replace both occurrences that set communityI18n
with calls to getCommunityI18nContext(community.language) and ensure the helper
initializes the I18n with defaultLanguageOnMissing, defaultLanguage and
directory exactly as currently used.
- Around line 749-757: Remove the unnecessary ObjectId cast and non-null
assertion and avoid passing the community language twice: in both publishBuy and
publishSell replace Community.findOne({ _id: new ObjectId(order.community_id!)
}) with Community.findOne({ _id: order.community_id }) (no casting or "!"), and
when constructing communityI18n keep defaultLanguage: community.language but
call createContext with only the language argument (e.g.,
.createContext(community.language)) so the language value isn't duplicated.
- Around line 1207-1995: Multiple places in this file pass order._id into
i18n.t() without explicitly stringifying it; standardize all such usages to use
order._id.toString() for consistency. Search for functions that interpolate
order._id (e.g., successCancelOrderMessage, counterPartyCancelOrderMessage,
okCooperativeCancelMessage, wizardAddInvoiceExitMessage, expiredOrderMessage,
toBuyerPendingPaymentFailedMessage, toAdminChannelPendingPaymentSuccessMessage,
toAdminChannelPendingPaymentFailedMessage and any other helpers in this file)
and replace the argument passed to i18n.t() from order._id to
order._id.toString() everywhere it appears. Ensure you only change the
interpolation value (keep surrounding i18n keys and options intact) and run
tests/lint to confirm no type issues.

In `@bot/modules/orders/scenes.ts`:
- Line 78: Replace the brittle "community && community._id.toString()"
expression with optional chaining to consistently yield undefined when community
is missing; update the property to use "community?._id?.toString()" (reference:
the community_id assignment in the object where community is referenced).

In `@bot/validations.ts`:
- Line 25: Remove the stray migration breadcrumb comment "// Removed redundant
ObjectId declaration" from bot/validations.ts; locate that exact comment string
in the file and delete it so only actual code and meaningful comments remain
before merging.
- Around line 605-607: The _id filter is being set as a raw string in the
conditional (where._id = orderId) which is inconsistent with other branches that
use new ObjectId(orderId); update that assignment to construct an ObjectId
(i.e., where._id = new ObjectId(orderId)) using the same ObjectId constructor
used in the other branches (see the usages around lines building _id with new
ObjectId) so the filter style matches and avoid refactor hazards.
- Line 523: The local query variable `where` was loosened to `any`, regressing
type-safety; change its type back to a Mongoose FilterQuery for orders and allow
the $and/$or compound shapes—e.g. replace `let where: any = {` with something
like `let where: FilterQuery<IOrder> & ({ $and?: FilterQuery<IOrder>[]; $or?:
FilterQuery<IOrder>[] }) = {` (and apply the same typed change at the other
occurrences flagged around the file), so you retain strict typing for fields
while permitting compound operators.

In `@db_connect.ts`:
- Line 18: Remove the unnecessary cast and empty options when connecting to
MongoDB: replace the call to mongoose.connect(MONGO_URI, {} as ConnectOptions)
with mongoose.connect(MONGO_URI) and delete the now-unused ConnectOptions
import; ensure you update any imports or references so only MONGO_URI and
mongoose are used in db_connect.ts.

In `@jobs/pending_payments.ts`:
- Around line 13-15: Export the getPaymentAttempts helper so the env-parsing
logic is centralized and reused instead of duplicated; move or re-create
getPaymentAttempts in util/index.ts, export it, and replace the three ad-hoc
uses (getPaymentAttempts in jobs/pending_payments.ts plus the calls in
bot/start.ts, bot/modules/community/messages.ts, and
bot/modules/community/scenes.ts) to import and call the shared function; update
the implementation to use an explicit check like const n =
Number(process.env.PAYMENT_ATTEMPTS); return Number.isFinite(n) ? n : 2 so
PAYMENT_ATTEMPTS="0" yields 0 while invalid/absent values fall back to 2.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: e08fa77d-fbf5-43ea-829e-36c885d14a8c

📥 Commits

Reviewing files that changed from the base of the PR and between 064ded1 and d60b29e.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (26)
  • bot/commands.ts
  • bot/messages.ts
  • bot/modules/block/commands.ts
  • bot/modules/community/commands.ts
  • bot/modules/community/messages.ts
  • bot/modules/community/scenes.ts
  • bot/modules/dispute/actions.ts
  • bot/modules/dispute/commands.ts
  • bot/modules/nostr/events.ts
  • bot/modules/orders/commands.ts
  • bot/modules/orders/messages.ts
  • bot/modules/orders/scenes.ts
  • bot/modules/orders/takeOrder.ts
  • bot/ordersActions.ts
  • bot/scenes.ts
  • bot/start.ts
  • bot/validations.ts
  • db_connect.ts
  • jobs/pending_payments.ts
  • models/community.ts
  • models/order.ts
  • models/user.ts
  • package.json
  • tsconfig.json
  • util/communityHelper.ts
  • util/index.ts

Comment thread bot/commands.ts
Comment on lines +1 to 3
import mongoose from 'mongoose';
const { ObjectId } = mongoose.Types;
import { validateFiatSentOrder, validateReleaseOrder } from './validations';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix ESLint import/first failure breaking CI.

Pipeline reports: ESLint (import/first): Import in body of module; reorder to top. The const { ObjectId } = mongoose.Types; declaration on line 2 sits between two import statements; move it below all imports.

As per coding guidelines: "address warnings instead of disabling them".

🔧 Proposed fix
 import mongoose from 'mongoose';
-const { ObjectId } = mongoose.Types;
 import { validateFiatSentOrder, validateReleaseOrder } from './validations';
 import {
   createHoldInvoice,
@@ -27,6 +26,8 @@
 import { HasTelegram, MainContext } from './start';
 import { CommunityContext } from './modules/community/communityContext';
 
+const { ObjectId } = mongoose.Types;
+
 enum UserOrderRole {
📝 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
import mongoose from 'mongoose';
const { ObjectId } = mongoose.Types;
import { validateFiatSentOrder, validateReleaseOrder } from './validations';
import mongoose from 'mongoose';
import { validateFiatSentOrder, validateReleaseOrder } from './validations';
🧰 Tools
🪛 ESLint

[error] 3-3: Import in body of module; reorder to top.

(import/first)

🪛 GitHub Actions: Auto Check Lint

[error] 3-3: ESLint (import/first): Import in body of module; reorder to top.

🪛 GitHub Check: Lint

[failure] 3-3:
Import in body of module; reorder to top

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bot/commands.ts` around lines 1 - 3, Move the in-module declaration so all
imports come first: place "const { ObjectId } = mongoose.Types;" after the
import statements (below the imports of mongoose and the named validators like
validateFiatSentOrder and validateReleaseOrder) so that ObjectId is still
derived from mongoose but no longer sits between import lines, resolving the
import/first ESLint error.

Comment thread bot/commands.ts
const orderId = (ctx.update as any).callback_query.message.text;
if (!orderId) return;
order = await Order.findOne({ _id: orderId });
order = await Order.findOne({ _id: new ObjectId(orderId) });
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inventory all `new ObjectId(...)` usages introduced in the PR for cross-file consistency review.
rg -nP '\bnew\s+ObjectId\s*\(' --type=ts -C1

Repository: lnp2pBot/bot

Length of output: 8827


🏁 Script executed:

fd -t f '\.ts$' -path '*/models/*' | head -20

Repository: lnp2pBot/bot

Length of output: 225


🏁 Script executed:

rg -l "interface IOrder|class Order|model.*Order" --type=ts -m 5

Repository: lnp2pBot/bot

Length of output: 92


🏁 Script executed:

# Find Order model definition
git ls-files | grep -i 'order' | grep -E 'models|schema' | head -10

Repository: lnp2pBot/bot

Length of output: 72


🏁 Script executed:

cat -n models/order.ts

Repository: lnp2pBot/bot

Length of output: 6007


🏁 Script executed:

rg -l "interface IUser|export.*User|model.*User" --type=ts models/ | head -5

Repository: lnp2pBot/bot

Length of output: 107


🏁 Script executed:

cat -n models/user.ts

Repository: lnp2pBot/bot

Length of output: 2230


🏁 Script executed:

# Test what happens with new ObjectId(null)
python3 << 'EOF'
# Since we can't run MongoDB/Mongoose here, let's at least verify the MongoDB ObjectId behavior
# by checking if we have access to mongodb package

import subprocess
import sys

# Try to see if mongodb is available
try:
    result = subprocess.run(['npm', 'list', 'mongodb'], cwd='.', capture_output=True, text=True)
    print("NPM mongodb search:")
    print(result.stdout)
    print(result.stderr)
except Exception as e:
    print(f"Error: {e}")
EOF

Repository: lnp2pBot/bot

Length of output: 126


🏁 Script executed:

# Check package.json to see mongodb/mongoose versions
cat -n package.json | head -50

Repository: lnp2pBot/bot

Length of output: 1711


🏁 Script executed:

# Search for any tests related to ObjectId or null handling
rg "ObjectId\(null\)" --type=ts

Repository: lnp2pBot/bot

Length of output: 38


🏁 Script executed:

# Look for test cases that might reveal expected behavior
rg -A5 "new ObjectId" tests/ --type=ts | head -60

Repository: lnp2pBot/bot

Length of output: 2630


🏁 Script executed:

# Check if there's any Mongoose usage documentation or comments
rg -B2 -A2 "auto.cast|auto-cast" --type=ts

Repository: lnp2pBot/bot

Length of output: 38


🏁 Script executed:

# Look for how buyer_id and seller_id are used after being retrieved
rg -B2 -A2 "buyer_id\!|seller_id\!" bot/commands.ts | head -80

Repository: lnp2pBot/bot

Length of output: 153


🏁 Script executed:

# Check if there's any validation before using these fields
rg "buyer_id|seller_id" bot/commands.ts -B1 -A1 | grep -E "if.*buyer_id|if.*seller_id" | head -20

Repository: lnp2pBot/bot

Length of output: 505


🏁 Script executed:

# Look at the actual code patterns around line 151 where the review starts
sed -n '140,160p' bot/commands.ts

Repository: lnp2pBot/bot

Length of output: 717


🏁 Script executed:

# Check if there are cases where buyer_id or seller_id could be null at the point of use
rg -B5 "new ObjectId\(order\.buyer_id" bot/commands.ts | head -40

Repository: lnp2pBot/bot

Length of output: 1552


🏁 Script executed:

# Look for how order.buyer_id is checked before being used
rg -B10 "new ObjectId\(order\.buyer_id" bot/commands.ts | grep -A10 "order =" | head -50

Repository: lnp2pBot/bot

Length of output: 1522


🏁 Script executed:

# Create a simple test to check ObjectId(null) behavior
python3 << 'EOF'
# Let's check what the mongodb/bson ObjectId library does with null
# First, let's look at package-lock.json or npm ls to see what's installed

import subprocess
import json

result = subprocess.run(['npm', 'ls', '--json'], capture_output=True, text=True)
try:
    data = json.loads(result.stdout)
    # Find mongodb or bson
    for key in ['mongodb', 'bson']:
        if key in data.get('dependencies', {}):
            print(f"{key}: {data['dependencies'][key]}")
except:
    print("Could not parse npm ls output")

# Also check package.json dependencies
with open('package.json') as f:
    pkg = json.load(f)
    deps = pkg.get('dependencies', {})
    if 'mongoose' in deps:
        print(f"mongoose: {deps['mongoose']}")
EOF

Repository: lnp2pBot/bot

Length of output: 73


🏁 Script executed:

# Search for how ObjectId is imported and used - check if it's the mongodb ObjectId
rg "import.*ObjectId|from.*ObjectId" --type=ts | head -10

Repository: lnp2pBot/bot

Length of output: 160


🏁 Script executed:

# Let's check what happens when we look at the actual usage patterns
# Find all instances where buyer_id or seller_id are used after being wrapped in ObjectId
rg -B3 -A3 'new ObjectId\(order\.(buyer_id|seller_id)' bot/commands.ts | head -100

Repository: lnp2pBot/bot

Length of output: 3316


🏁 Script executed:

# Check if there are any defensive checks before using these fields
sed -n '230,245p' bot/commands.ts

Repository: lnp2pBot/bot

Length of output: 617


🏁 Script executed:

# Look for similar patterns in other files to understand if this is intentional
rg -B2 'new ObjectId\(.*\.buyer_id' bot/validations.ts jobs/pending_payments.ts

Repository: lnp2pBot/bot

Length of output: 267


🏁 Script executed:

# Check if there's error handling that catches ObjectId construction errors
rg -B2 -A2 "try.*new ObjectId|catch.*Error.*ObjectId" --type=ts

Repository: lnp2pBot/bot

Length of output: 38


🏁 Script executed:

# Look for any comments or documentation about ObjectId usage in the codebase
rg -i "objectid.*null|null.*objectid" --type=ts -B1 -A1

Repository: lnp2pBot/bot

Length of output: 38


🏁 Script executed:

# Check what version of mongodb driver is being used (indirectly through mongoose)
cat -n package-lock.json | grep -A5 -B5 '"bson"' | head -30

Repository: lnp2pBot/bot

Length of output: 565


🏁 Script executed:

# Let's create a test to verify ObjectId behavior with null
python3 << 'EOF'
# We need to understand what ObjectId(null) actually does
# Let's check the mongoose/mongodb documentation behavior

# Since we're on Mongoose 9.5.0, let's look at what that version does
# by examining node_modules if available

import subprocess
import os

# Check if node_modules exists and has mongodb
result = subprocess.run(['find', 'node_modules', '-name', 'index.d.ts', '-path', '*bson*'], 
                       capture_output=True, text=True, timeout=5)
if result.stdout:
    print("Found BSON type definitions:")
    files = result.stdout.strip().split('\n')[:3]
    for f in files:
        print(f)
        
# Try to find ObjectId constructor definition
result = subprocess.run(['grep', '-r', 'class ObjectId', 'node_modules/bson/', '--include=*.js', '--include=*.d.ts'], 
                       capture_output=True, text=True, timeout=5)
if result.returncode == 0 and result.stdout:
    print("\nObjectId definition found:")
    print(result.stdout[:500])
EOF

Repository: lnp2pBot/bot

Length of output: 38


🏁 Script executed:

# Check what the actual behavior is - look for error handling around ObjectId construction
rg "new ObjectId" bot/commands.ts -B5 -A5 | grep -E "try|catch|throw" -A2 -B2 | head -40

Repository: lnp2pBot/bot

Length of output: 1672


🏁 Script executed:

# Look for any specific Mongoose 9 changes or notes in comments
rg -i "mongoose.*9|version.*9|auto.*cast" --type=ts --type=md

Repository: lnp2pBot/bot

Length of output: 38


🏁 Script executed:

# Verify if plain strings work by checking if there are any examples without ObjectId wrapping
rg "findOne.*{.*_id.*:" bot/validations.ts -A1 | grep -v "new ObjectId" | head -20

Repository: lnp2pBot/bot

Length of output: 167


🏁 Script executed:

# Search for findOne patterns more carefully
rg "findOne.*_id" bot/validations.ts -A1 | head -30

Repository: lnp2pBot/bot

Length of output: 589


🏁 Script executed:

# Check if there are any successful queries without ObjectId wrapping
rg "\.findOne\(" --type=ts -A1 | grep -v "new ObjectId" | head -40

Repository: lnp2pBot/bot

Length of output: 2305


🏁 Script executed:

# Look at the test file to see how mocked queries handle this
rg "findOne" tests/bot/validation.spec.ts -B2 -A2 | head -60

Repository: lnp2pBot/bot

Length of output: 1524


🏁 Script executed:

# This is crucial - check if there's a case where buyer_id or seller_id could be null at runtime
# Look for all places where an order is created/fetched, then used
rg -B5 "buyer_id.*null" models/order.ts

Repository: lnp2pBot/bot

Length of output: 204


🏁 Script executed:

# Check the ln/subscribe_invoice.ts file more carefully since it doesn't wrap in ObjectId
sed -n '1,50p' ln/subscribe_invoice.ts

Repository: lnp2pBot/bot

Length of output: 1536


🏁 Script executed:

# Look for what happens in subscribe_invoice with buyer_id/seller_id
sed -n '80,120p' ln/subscribe_invoice.ts

Repository: lnp2pBot/bot

Length of output: 1535


🏁 Script executed:

# Find the critical part where buyer_id and seller_id are used without ObjectId wrapping
rg "buyer_id|seller_id" ln/subscribe_invoice.ts -B3 -A3

Repository: lnp2pBot/bot

Length of output: 1109


🏁 Script executed:

# Check if ln/subscribe_invoice is using a different pattern - look for the actual queries
grep -n "User.findOne" ln/subscribe_invoice.ts -A1 -B1

Repository: lnp2pBot/bot

Length of output: 729


🏁 Script executed:

# Let me see the full context around those User.findOne calls
sed -n '140,160p' ln/subscribe_invoice.ts

Repository: lnp2pBot/bot

Length of output: 1144


🏁 Script executed:

# Check if there's any validation that ensures buyer_id/seller_id are not null before use
rg -B10 "User.findOne.*buyer_id" ln/subscribe_invoice.ts | grep -E "if.*buyer_id|if.*null|!=="

Repository: lnp2pBot/bot

Length of output: 94


🏁 Script executed:

# Also check the pattern in bot/validations.ts to see if they check before wrapping
rg -B5 "new ObjectId\(.*Id\)" bot/validations.ts | head -40

Repository: lnp2pBot/bot

Length of output: 578


Remove unnecessary new ObjectId() wrappings on string-typed ID fields — Mongoose 9 auto-casts them.

The codebase has two patterns: ln/subscribe_invoice.ts queries User.findOne({ _id: order.buyer_id }) directly (no ObjectId wrapper), while bot/commands.ts wraps the same fields with new ObjectId(order.buyer_id!). Both work identically in Mongoose 9, proving auto-casting.

Wrapping adds two problems:

  1. Silent miss on null values. order.buyer_id is typed string | null. When null reaches new ObjectId(null), it generates a random ObjectId instead of failing — converting a legitimate "not found" into a silent missed query. Direct strings let Mongoose's caster handle the null naturally.
  2. Non-null assertion masks the type. The ! silences TypeScript but doesn't guarantee runtime safety. If buyer_id is null at runtime, the assertion is false.

Remove the new ObjectId() wrappings from the listed lines. If buyer_id or seller_id can legitimately be null, add explicit guards before queries:

if (!order.buyer_id) return;
const buyer = await User.findOne({ _id: order.buyer_id });

Applies to: 142, 151, 180, 233, 236, 238, 305, 315, 324, 326, 418, 422, 512, 521, 523, 651, 656, 742, 745, 835 in bot/commands.ts and similar patterns in bot/validations.ts, jobs/pending_payments.ts, bot/messages.ts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bot/commands.ts` at line 142, Remove the unnecessary new ObjectId(...)
wrappings around string-typed ID fields (e.g. places calling Order.findOne({
_id: new ObjectId(orderId) }) and User.findOne({ _id: new
ObjectId(order.buyer_id!) })) because Mongoose 9 auto-casts strings; instead
pass the raw string IDs to findOne. Replace occurrences that wrap
order.buyer_id, order.seller_id, orderId, etc., with the plain values and add
explicit null/undefined guards where the field is typed string | null (for
example, if (!order.buyer_id) return; const buyer = await User.findOne({ _id:
order.buyer_id });). Update all listed call sites in bot/commands.ts (and
similar patterns in bot/validations.ts, jobs/pending_payments.ts,
bot/messages.ts) to remove new ObjectId and drop non-null assertions so runtime
nulls are handled explicitly.

Comment thread bot/commands.ts Outdated
}
if (user.banned) return await messages.bannedUserErrorMessage(ctx, user);
const order = await ordersActions.getOrder(ctx, user, orderId);
const order = await ordersActions.getOrder(ctx, user, new ObjectId(orderId) as any);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In Mongoose 9.5, does Model.findOne({ _id: '<24-hex-string>' }) still auto-cast the string to ObjectId, or is explicit new mongoose.Types.ObjectId(...) required?

💡 Result:

Yes, in Mongoose 9.5, Model.findOne({ _id: '<24-hex-string>' }) still auto-casts the string to ObjectId. Explicit new mongoose.Types.ObjectId(...) is not required.

Citations:


🏁 Script executed:

# First, check the bot/commands.ts file around lines 685 and 905
head -n 710 bot/commands.ts | tail -n +670 | cat -n

Repository: lnp2pBot/bot

Length of output: 1809


🏁 Script executed:

# Check around line 905 too
head -n 920 bot/commands.ts | tail -n +890 | cat -n

Repository: lnp2pBot/bot

Length of output: 1145


🏁 Script executed:

# Find the ordersActions.getOrder function definition
rg "getOrder\(" bot/ --type ts -A 10 | head -100

Repository: lnp2pBot/bot

Length of output: 1138


🏁 Script executed:

# Find the ordersActions module and getOrder definition
fd "ordersActions" bot/ --type ts | head -5

Repository: lnp2pBot/bot

Length of output: 226


🏁 Script executed:

# Search more broadly for getOrder definition
rg "const getOrder|export.*getOrder|getOrder\s*=" bot/ --type ts -B 2 -A 15

Repository: lnp2pBot/bot

Length of output: 3605


🏁 Script executed:

# Get the complete getOrder implementation
rg "const getOrder = async" bot/ordersActions.ts -A 30

Repository: lnp2pBot/bot

Length of output: 704


Remove new ObjectId() wrapping and type-casting that bypasses getOrder's validation.

getOrder expects an orderId: string parameter and validates it via ObjectId.isValid(orderId) before showing the user-facing notValidIdMessage on invalid input. Wrapping orderId in new ObjectId(...) as any causes the constructor to throw synchronously on invalid 24-hex strings, bypassing validation and user-facing error messaging. The outer catch (error) { logger.error(error); } swallows the exception silently—a UX regression. Mongoose 9.5 still auto-casts string filters to ObjectId for schema-typed _id fields, so pass the string directly.

🔧 Proposed fix
-    const order = await ordersActions.getOrder(ctx, user, new ObjectId(orderId) as any);
+    const order = await ordersActions.getOrder(ctx, user, orderId);

(apply at both line 685 and line 905)

📝 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 order = await ordersActions.getOrder(ctx, user, new ObjectId(orderId) as any);
const order = await ordersActions.getOrder(ctx, user, orderId);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bot/commands.ts` at line 685, The call currently constructs and casts an
ObjectId around orderId which bypasses ordersActions.getOrder's own validation;
update both occurrences (the call at the order retrieval near the earlier block
and the second similar call) to pass the raw string orderId directly to
ordersActions.getOrder so its ObjectId.isValid(orderId) check and user-facing
notValidIdMessage remain effective (remove new ObjectId(orderId) as any and pass
orderId: string to getOrder).

Comment thread bot/commands.ts Outdated
throw new Error('counterPartyUser was not found');

const updateOrder = await setCooperativeCancelFlag(order._id, initiator);
const updateOrder = await setCooperativeCancelFlag(new ObjectId(order._id) as any, initiator);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Drop the as any cast — setCooperativeCancelFlag accepts a string and order._id already serializes cleanly.

setCooperativeCancelFlag(orderId: string, ...) is typed for a string, but the call passes new ObjectId(order._id) as anyorder._id is already a mongoose.Types.ObjectId, so the wrapping is a no-op other than forcing an unsafe cast. Use order._id.toString() and let the function's _id: new ObjectId(orderId) (line 46) do the conversion, or better, also drop that wrapping and rely on Mongoose's auto-cast.

🔧 Proposed fix
-    const updateOrder = await setCooperativeCancelFlag(new ObjectId(order._id) as any, initiator);
+    const updateOrder = await setCooperativeCancelFlag(order._id.toString(), initiator);
📝 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 updateOrder = await setCooperativeCancelFlag(new ObjectId(order._id) as any, initiator);
const updateOrder = await setCooperativeCancelFlag(order._id.toString(), initiator);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bot/commands.ts` at line 751, The call to setCooperativeCancelFlag currently
wraps order._id with new ObjectId(...) and casts to any; remove the unsafe cast
and pass a string instead (e.g., use order._id.toString() or pass order._id
directly if already a string) so the call becomes
setCooperativeCancelFlag(order._id.toString(), initiator); update callers to
stop using "as any" and, optionally, remove the redundant new ObjectId(orderId)
conversion inside setCooperativeCancelFlag if you want to rely on Mongoose
auto-casting.

Comment on lines 159 to 163
const isScheduled = await PendingPayment.findOne({
community_id: communityId,
attempts: { $lt: process.env.PAYMENT_ATTEMPTS },
attempts: { $lt: Number(process.env.PAYMENT_ATTEMPTS) },
paid: false,
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Number(process.env.PAYMENT_ATTEMPTS) can become NaN and silently disable the duplicate-payment guard.

If PAYMENT_ATTEMPTS is unset/invalid, Number(...) is NaN and attempts: { $lt: NaN } matches no documents, so isScheduled will be null and the "invoice already being paid" guard is bypassed — allowing parallel withdrawal attempts.

This same logic now exists in jobs/pending_payments.ts as getPaymentAttempts() (with a || 2 fallback). Consider exporting that helper and reusing it here (and in bot/modules/community/scenes.ts:947-951, bot/start.ts:1018-1021) for a single source of truth.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bot/modules/community/messages.ts` around lines 159 - 163, The guard using
Number(process.env.PAYMENT_ATTEMPTS) can become NaN and bypass the
duplicate-payment check; replace that ad-hoc conversion by importing and using
the shared getPaymentAttempts() helper from jobs/pending_payments.ts (export it
if not already exported) and use it in the PendingPayment.findOne query (the
same change should be applied to bot/modules/community/scenes.ts around the
withdrawal logic and bot/start.ts where PAYMENT_ATTEMPTS is used) so attempts
uses a validated numeric fallback (e.g., || 2) instead of
Number(process.env.PAYMENT_ATTEMPTS).

Comment thread bot/scenes.ts
order_id: order._id,
attempts: { $lt: process.env.PAYMENT_ATTEMPTS },
order_id: order._id.toString(),
attempts: { $lt: Number(process.env.PAYMENT_ATTEMPTS) },
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Number(process.env.PAYMENT_ATTEMPTS) silently becomes NaN if the env var is missing.

$lt: NaN will never match any document, which would defeat this duplicate‑payment guard and let a second PendingPayment slip through. Consider validating the variable at startup (or here) and failing fast, mirroring the existing pattern used for HOLD_INVOICE_EXPIRATION_WINDOW above.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bot/scenes.ts` at line 149, The filter uses
Number(process.env.PAYMENT_ATTEMPTS) which can become NaN and make the $lt check
always fail; define and validate a PAYMENT_ATTEMPTS constant at startup (like
the existing HOLD_INVOICE_EXPIRATION_WINDOW pattern) by parsing
process.env.PAYMENT_ATTEMPTS (e.g., parseInt) and throwing/failing fast if it is
missing or NaN, then replace Number(process.env.PAYMENT_ATTEMPTS) in the
attempts: { $lt: ... } expression with that validated PAYMENT_ATTEMPTS constant
so the duplicate‑payment guard works reliably.

Comment thread bot/start.ts Outdated
Comment on lines +6 to +7
import mongoose from 'mongoose';
const { ObjectId } = mongoose.Types;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

ObjectId is imported but never used — remove the destructure (and the mongoose import if unused).

ESLint flags this; the file only does _id.toString() and string equality—ObjectId is never referenced. As per coding guidelines: "Follow ESLint Standard plus TypeScript rules and address warnings instead of disabling them".

🧹 Proposed fix
-import mongoose from 'mongoose';
-const { ObjectId } = mongoose.Types;
📝 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
import mongoose from 'mongoose';
const { ObjectId } = mongoose.Types;
import mongoose from 'mongoose';
🧰 Tools
🪛 ESLint

[error] 7-7: 'ObjectId' is assigned a value but never used. Allowed unused vars must match /^_/u.

(@typescript-eslint/no-unused-vars)


[error] 7-7: 'ObjectId' is assigned a value but never used.

(no-unused-vars)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bot/start.ts` around lines 6 - 7, Remove the unused destructure "ObjectId"
(and the "mongoose" import if it's only present to obtain ObjectId) from this
module: locate the import line that reads "import mongoose from 'mongoose';
const { ObjectId } = mongoose.Types;" and delete the ObjectId destructure (and
the whole mongoose import if no other symbol from mongoose is referenced
elsewhere in the file); ensure any existing code that uses _id.toString() or
string comparisons continues to work without ObjectId, adjust imports/types if
necessary, and run ESLint to confirm the warning is resolved.

Comment thread jobs/pending_payments.ts
});
for (const pending of pendingPayments) {
const order = await Order.findOne({ _id: pending.order_id });
const order = await Order.findOne({ _id: new ObjectId(pending.order_id) });
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Does Mongoose 9.5 still auto-cast string filter values to ObjectId for schema-typed _id fields, or does it require explicit new ObjectId(...) wrapping?

💡 Result:

Yes, Mongoose 9.5 still auto-casts string filter values to ObjectId for schema-typed _id fields. It does not require explicit new ObjectId(...) wrapping.

Citations:


🏁 Script executed:

# Locate and examine jobs/pending_payments.ts
fd -t f "pending_payments.ts"

Repository: lnp2pBot/bot

Length of output: 81


🏁 Script executed:

# Find the Order model/interface definition
fd -t f -e ts | xargs rg -l "interface IOrder|class Order" | head -20

Repository: lnp2pBot/bot

Length of output: 92


🏁 Script executed:

# Check for the exact lines mentioned in the review
fd -t f "pending_payments.ts" -exec wc -l {} \;

Repository: lnp2pBot/bot

Length of output: 243


🏁 Script executed:

cat -n jobs/pending_payments.ts | head -100

Repository: lnp2pBot/bot

Length of output: 4569


🏁 Script executed:

cat -n models/order.ts | head -150

Repository: lnp2pBot/bot

Length of output: 6007


🏁 Script executed:

cat -n jobs/pending_payments.ts | sed -n '190,220p'

Repository: lnp2pBot/bot

Length of output: 1553


🏁 Script executed:

fd -t f -e ts "pending_payment" models/

Repository: lnp2pBot/bot

Length of output: 82


🏁 Script executed:

fd -t f -e ts "user\\.ts" models/

Repository: lnp2pBot/bot

Length of output: 71


🏁 Script executed:

cat -n models/pending_payment.ts

Repository: lnp2pBot/bot

Length of output: 1741


🏁 Script executed:

cat -n models/user.ts | head -100

Repository: lnp2pBot/bot

Length of output: 2230


Remove unnecessary new ObjectId() wrapping and explicit null checks for nullable fields on lines 63 and 87.

Mongoose 9.5 still auto-casts string values to ObjectId in filters without explicit wrapping, so new ObjectId(...) is unnecessary throughout. More critically, lines 63 and 87 use non-null assertions (buyer_id! and seller_id!) on fields typed as string | null in the IOrder interface. At runtime, if either is null, new ObjectId(null) generates a random ObjectId instead of failing, leading to a silent match against the wrong user. Replace the assertions with explicit null checks:

if (!order.buyer_id) throw Error('buyer_id is required');
const buyerUser = await User.findOne({ _id: order.buyer_id });

Or simplify to:

const buyerUser = await User.findOne({ _id: order.buyer_id });
if (buyerUser === null) throw Error('buyerUser was not found in DB');

Apply the same pattern to line 87 for seller_id.

Also applies to: 28-28, 197-197, 209-209 (remove unnecessary wrapping)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jobs/pending_payments.ts` at line 28, Remove unnecessary use of new
ObjectId(...) when querying (e.g., Order.findOne({ _id: new
ObjectId(pending.order_id) }) and other similar queries) and instead pass the
string directly so Mongoose can auto-cast; additionally, replace non-null
assertions on nullable fields buyer_id and seller_id with explicit null checks
and proper error handling before querying (e.g., verify order.buyer_id and
order.seller_id are present and throw or return a clear error if missing), then
call User.findOne({ _id: order.buyer_id }) / User.findOne({ _id: order.seller_id
}) and handle the case where the returned buyerUser or sellerUser is null.

Comment thread package.json
"invoices": "2.0.6",
"lightning": "10.25.0",
"mongoose": "^8.17.1",
"mongoose": "^9.5.0",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

What is the minimum supported Node.js version for mongoose 9.x?

💡 Result:

The minimum supported Node.js version for Mongoose 9.x is 18.

Citations:


🏁 Script executed:

# Check the latest Mongoose 9.x versions available on npm
npm view mongoose@9 versions --json 2>/dev/null | tail -20

Repository: lnp2pBot/bot

Length of output: 295


🏁 Script executed:

# Also check the specific version metadata to see if 9.5.0 exists and Node requirements
npm view mongoose@9.5.0 2>/dev/null || echo "9.5.0 does not exist"
npm view mongoose@9.4.1 engines 2>/dev/null
npm view mongoose@9.0.0 engines 2>/dev/null

Repository: lnp2pBot/bot

Length of output: 953


🏁 Script executed:

# Check the current package.json to see engines declaration
head -50 package.json | grep -A 2 '"engines"'

Repository: lnp2pBot/bot

Length of output: 99


Mongoose 9.5.0 has incompatible Node engine requirements with the project.

One blocking problem:

Mongoose 9.5.0 requires Node.js ≥ 20.19.0 but engines.node here declares >=18.0.0. Bump the engine constraint to >=20.19.0 to resolve the incompatibility and update the Node version in your CI pipeline accordingly.

Note: Mongoose 9.0.0 introduced several breaking changes (driver upgrade, removed deprecated options/types, schema/Document casting changes). Please review the migration guide and add regression testing before merging — this is more than a simple version bump.

🧰 Tools
🪛 GitHub Actions: Auto Check Lint

[warning] npm EBADENGINE: Unsupported engine(s) detected (node v18.20.8 current, required >=20.x for bson/ecpair/mongodb/mongoose and others).


[warning] npm audit: 25 vulnerabilities found (4 low, 7 moderate, 14 high).


[warning] npm deprecated dependencies detected (e.g., rimraf@3.0.2, inflight@1.0.6, glob@7.2.0, crypto@1.0.1, eslint@8.57.1, etc.).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` at line 31, package.json currently depends on "mongoose":
"^9.5.0" which requires Node.js >=20.19.0 but the package.json "engines.node"
declares >=18.0.0; update the "engines.node" constraint to ">=20.19.0" and align
your CI workflow (node version in CI config) to use >=20.19.0, then run/author
regression tests around code paths using mongoose (and review mongoose 9.x
migration notes) to catch breaking changes before merging; look for the
"mongoose" dependency and the "engines.node" field to apply these changes.

@knocte
Copy link
Copy Markdown
Collaborator

knocte commented Apr 26, 2026

Mongoose 9.5.0 requires Node.js ≥ 20.19.0 but engines.node here declares >=18.0.0

This is because CI uses latest Ubuntu LTS, which right now is 24.04, which has NodeJS 18.x.

IMO this PR shouldn't merged before May. This way, you can upgrade CI first to 26.04 when it's released (in theory it will be very soon, in late April), which will bring NodeJS 22.x.

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