From f49c466201948f2664bef10f134ad1e391712e40 Mon Sep 17 00:00:00 2001 From: Tori Date: Wed, 13 May 2026 22:41:17 -0500 Subject: [PATCH 01/12] feat: upgrade mongoose from 6.13.6 to 9.6.2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Staged upgrade through 7.x, 8.x, and 9.x verifying 147/147 tests pass at each major version. Breaking changes resolved: - Remove incorrect `_id: string` redeclarations from Document interfaces (UserDocument, IOrder, ICommunity); mongoose 8+ enforces ObjectId type - Add explicit `id: string` virtual to document interfaces (mongoose 9) - Replace `FilterQuery` with `QueryFilter` (renamed in mongoose 9) - Add `.toString()` on `_id` assignments and query filters across 11 files where ObjectId was used in String schema fields - Fix `getOrderCountByCommunity` return type: `number[]` → `Record` - Convert `process.env.PAYMENT_ATTEMPTS` to Number in `$lt` query operators - Convert `tgUser.id` (number) to String in User.findOne query filter - Add `skipLibCheck: true` to tsconfig to bypass mongoose 9 internal type recursion bug in populate.d.ts Closes #655 --- bot/commands.ts | 8 +- bot/messages.ts | 8 +- bot/modules/community/commands.ts | 12 +-- bot/modules/community/messages.ts | 2 +- bot/modules/community/scenes.ts | 12 +-- bot/modules/dispute/actions.ts | 4 +- bot/modules/dispute/commands.ts | 12 +-- bot/modules/orders/commands.ts | 2 +- bot/modules/orders/takeOrder.ts | 4 +- bot/ordersActions.ts | 4 +- bot/scenes.ts | 4 +- bot/start.ts | 32 ++++---- bot/validations.ts | 26 +++--- jobs/pending_payments.ts | 4 +- models/community.ts | 2 +- models/order.ts | 2 +- models/user.ts | 2 +- package-lock.json | 127 ++++++++++++++---------------- package.json | 2 +- tsconfig.json | 1 + util/communityHelper.ts | 2 +- util/index.ts | 8 +- 22 files changed, 134 insertions(+), 146 deletions(-) diff --git a/bot/commands.ts b/bot/commands.ts index 5d871c52..39b16afc 100644 --- a/bot/commands.ts +++ b/bot/commands.ts @@ -682,7 +682,7 @@ const cancelOrder = async ( if (order.hash) await cancelHoldInvoice({ hash: order.hash }); order.status = 'CANCELED'; - order.canceled_by = user._id; + order.canceled_by = user._id.toString(); await order.save(); OrderEvents.orderUpdated(order); // we sent a private message to the user @@ -739,7 +739,7 @@ const cancelOrder = async ( if (counterPartyUser == null) throw new Error('counterPartyUser was not found'); - const updateOrder = await setCooperativeCancelFlag(order._id, initiator); + const updateOrder = await setCooperativeCancelFlag(order._id.toString(), initiator); // If the call returns null, the flag was already set (or order is missing), // so we treat it as a duplicate request. @@ -766,7 +766,7 @@ const cancelOrder = async ( let seller = initiatorUser; let i18nCtxSeller = ctx.i18n; - if (order.seller_id == counterPartyUser._id) { + if (order.seller_id == counterPartyUser._id.toString()) { seller = counterPartyUser; i18nCtxSeller = i18nCtxCP; } @@ -861,7 +861,7 @@ const release = async ( const order = await validateReleaseOrder(ctx, user, orderId); if (!order) return; // We look for a dispute for this order - const dispute = await Dispute.findOne({ order_id: order._id }); + const dispute = await Dispute.findOne({ order_id: order._id.toString() }); if (dispute) { dispute.status = 'RELEASED'; diff --git a/bot/messages.ts b/bot/messages.ts index 17354be0..94352837 100644 --- a/bot/messages.ts +++ b/bot/messages.ts @@ -101,7 +101,7 @@ const invoicePaymentRequestMessage = async ( parse_mode: 'Markdown', }); - await ctx.telegram.sendMessage(user.tg_id, order._id, { + await ctx.telegram.sendMessage(user.tg_id, order._id.toString(), { reply_markup: { inline_keyboard: [ [ @@ -443,7 +443,7 @@ const beginTakeBuyMessage = async ( }, ]); - await bot.telegram.sendMessage(seller.tg_id, order._id, { + await bot.telegram.sendMessage(seller.tg_id, order._id.toString(), { reply_markup: { inline_keyboard: [ [ @@ -543,7 +543,7 @@ const onGoingTakeBuyMessage = async ( days: ageInDays, }), ); - await bot.telegram.sendMessage(buyer.tg_id, order._id, { + await bot.telegram.sendMessage(buyer.tg_id, order._id.toString(), { reply_markup: { inline_keyboard: [ [{ text: i18nBuyer.t('continue'), callback_data: 'addInvoiceBtn' }], @@ -575,7 +575,7 @@ const beginTakeSellMessage = async ( ctx.i18n.t('you_took_someone_order', { expirationTime }), { parse_mode: 'MarkdownV2' }, ); - await bot.telegram.sendMessage(buyer.tg_id, order._id, { + await bot.telegram.sendMessage(buyer.tg_id, order._id.toString(), { reply_markup: { inline_keyboard: [ [ diff --git a/bot/modules/community/commands.ts b/bot/modules/community/commands.ts index 78e60525..1c95e938 100644 --- a/bot/modules/community/commands.ts +++ b/bot/modules/community/commands.ts @@ -7,7 +7,7 @@ import { MainContext } from '../../start'; import { CommunityContext } from './communityContext'; import { Telegraf } from 'telegraf'; -async function getOrderCountByCommunity(): Promise { +async function getOrderCountByCommunity(): Promise> { const data = await Order.aggregate([ { $group: { _id: '$community_id', total: { $count: {} } } }, ]); @@ -57,7 +57,7 @@ export const setComm = async (ctx: MainContext) => { return await ctx.reply(ctx.i18n.t('community_not_found')); } - user.default_community_id = community._id; + user.default_community_id = community._id.toString(); await user.save(); await ctx.reply(ctx.i18n.t('operation_successful')); @@ -89,7 +89,7 @@ export const myComms = async (ctx: MainContext) => { try { const { user } = ctx; - const communities = await Community.find({ creator_id: user._id }); + const communities = await Community.find({ creator_id: user._id.toString() }); if (!communities.length) return await ctx.reply(ctx.i18n.t('you_dont_have_communities')); @@ -146,7 +146,7 @@ export const updateCommunity = async ( if (!(await validateObjectId(ctx, id))) return; const community = await Community.findOne({ _id: id, - creator_id: user._id, + creator_id: user._id.toString(), }); if (!community) { @@ -227,7 +227,7 @@ export const deleteCommunity = async (ctx: CommunityContext) => { if (!(await validateObjectId(ctx, id))) return; const community = await Community.findOne({ _id: id, - creator_id: ctx.user._id, + creator_id: ctx.user._id.toString(), }); if (!community) { @@ -250,7 +250,7 @@ export const changeVisibility = async (ctx: CommunityContext) => { if (!(await validateObjectId(ctx, id))) return; const community = await Community.findOne({ _id: id, - creator_id: ctx.user._id, + creator_id: ctx.user._id.toString(), }); if (!community) { diff --git a/bot/modules/community/messages.ts b/bot/modules/community/messages.ts index 94b2c8d6..41ec3d58 100644 --- a/bot/modules/community/messages.ts +++ b/bot/modules/community/messages.ts @@ -164,7 +164,7 @@ export const earningsMessage = async (ctx: MainContext) => { // We check if there is a payment scheduled for this community const isScheduled = await PendingPayment.findOne({ community_id: communityId, - attempts: { $lt: process.env.PAYMENT_ATTEMPTS }, + attempts: { $lt: Number(process.env.PAYMENT_ATTEMPTS) }, paid: false, }); if (isScheduled) diff --git a/bot/modules/community/scenes.ts b/bot/modules/community/scenes.ts index 79d9ed3c..0d6ff3ce 100644 --- a/bot/modules/community/scenes.ts +++ b/bot/modules/community/scenes.ts @@ -394,9 +394,9 @@ const createCommunitySteps = { const user = await User.findOne({ username }); if (user) { solvers.push({ - id: user._id, + id: user._id.toString(), username: user.username, - } as IUsernameId); + } as unknown as IUsernameId); } } } else { @@ -745,9 +745,9 @@ export const updateSolversCommunityWizard = new Scenes.WizardScene( if (user == null) throw new Error('user not found'); if (user) { solvers.push({ - id: user._id, + id: user._id.toString(), username: user.username, - } as IUsernameId); + } as unknown as IUsernameId); botUsers.push(username); } else { notBotUsers.push(username); @@ -1008,8 +1008,8 @@ export const addEarningsInvoiceWizard = new Scenes.WizardScene( return await ctx.reply(ctx.i18n.t('invoice_with_incorrect_amount')); const isScheduled = await PendingPayment.findOne({ - community_id: community._id, - attempts: { $lt: process.env.PAYMENT_ATTEMPTS }, + community_id: community._id.toString(), + attempts: { $lt: Number(process.env.PAYMENT_ATTEMPTS) }, paid: false, is_invoice_expired: false, }); diff --git a/bot/modules/dispute/actions.ts b/bot/modules/dispute/actions.ts index 7738fac3..69825d08 100644 --- a/bot/modules/dispute/actions.ts +++ b/bot/modules/dispute/actions.ts @@ -35,10 +35,10 @@ export const takeDispute = async (ctx: MainContext): Promise => { if (seller === null) throw new Error('seller not found'); const initiator = order.buyer_dispute ? 'buyer' : 'seller'; const buyerDisputes = await Dispute.countDocuments({ - $or: [{ buyer_id: buyer._id }, { seller_id: buyer._id }], + $or: [{ buyer_id: buyer._id.toString() }, { seller_id: buyer._id.toString() }], }); const sellerDisputes = await Dispute.countDocuments({ - $or: [{ buyer_id: seller._id }, { seller_id: seller._id }], + $or: [{ buyer_id: seller._id.toString() }, { seller_id: seller._id.toString() }], }); dispute.solver_id = solver.id; diff --git a/bot/modules/dispute/commands.ts b/bot/modules/dispute/commands.ts index 708419ee..42805bff 100644 --- a/bot/modules/dispute/commands.ts +++ b/bot/modules/dispute/commands.ts @@ -35,7 +35,7 @@ export const handleDispute = async (ctx: MainContext, orderId: string) => { const seller = await User.findOne({ _id: order.seller_id }); if (seller === null) throw new Error('seller was not found'); let initiator: 'seller' | 'buyer' = 'seller'; - if (user._id == order.buyer_id) initiator = 'buyer'; + if (user._id.toString() == order.buyer_id) initiator = 'buyer'; order.previous_dispute_status = order.status; if (initiator === 'seller') order.seller_dispute = true; @@ -53,11 +53,11 @@ export const handleDispute = async (ctx: MainContext, orderId: string) => { // If a user disputes is equal to MAX_DISPUTES, we ban the user const buyerDisputes = (await Dispute.countDocuments({ - $or: [{ buyer_id: buyer._id }, { seller_id: buyer._id }], + $or: [{ buyer_id: buyer._id.toString() }, { seller_id: buyer._id.toString() }], })) + 1; const sellerDisputes = (await Dispute.countDocuments({ - $or: [{ buyer_id: seller._id }, { seller_id: seller._id }], + $or: [{ buyer_id: seller._id.toString() }, { seller_id: seller._id.toString() }], })) + 1; const maxDisputes = Number(process.env.MAX_DISPUTES); // if MAX_DISPUTES is not specified or can't be parsed as number, following @@ -164,13 +164,13 @@ const deleteDispute = async (ctx: MainContext) => { // We check if this dispute is from a community we validate that // the solver is running this command - if (dispute && dispute.solver_id != admin._id) { + if (dispute && dispute.solver_id != admin._id.toString()) { return await globalMessages.notAuthorized(ctx); } } - if (user._id == dispute.buyer_id) dispute.buyer_id = null; - if (user._id == dispute.seller_id) dispute.seller_id = null; + if (user._id.toString() == dispute.buyer_id) dispute.buyer_id = null; + if (user._id.toString() == dispute.seller_id) dispute.seller_id = null; await dispute.save(); await ctx.reply(ctx.i18n.t('operation_successful')); diff --git a/bot/modules/orders/commands.ts b/bot/modules/orders/commands.ts index 75a9d385..b2e69d6b 100644 --- a/bot/modules/orders/commands.ts +++ b/bot/modules/orders/commands.ts @@ -217,7 +217,7 @@ async function enterWizard( const isMaxPending = async (user: UserDocument) => { const pendingOrders = await Order.countDocuments({ - creator_id: user._id, + creator_id: user._id.toString(), status: 'PENDING', }); const maxPendingOrders = process.env.MAX_PENDING_ORDERS; diff --git a/bot/modules/orders/takeOrder.ts b/bot/modules/orders/takeOrder.ts index ad574b32..884b950c 100644 --- a/bot/modules/orders/takeOrder.ts +++ b/bot/modules/orders/takeOrder.ts @@ -78,7 +78,7 @@ export const takebuy = async ( const { randomImage } = generateRandomImage(user._id.toString()); order.status = 'WAITING_PAYMENT'; - order.seller_id = user._id; + order.seller_id = user._id.toString(); order.taken_at = new Date(Date.now()); order.random_image = randomImage; @@ -129,7 +129,7 @@ export const takesell = async ( return await messages.bannedUserErrorMessage(ctx, user); if (!(await validateTakeSellOrder(ctx, bot, user, order))) return; order.status = 'WAITING_BUYER_INVOICE'; - order.buyer_id = user._id; + order.buyer_id = user._id.toString(); order.taken_at = new Date(Date.now()); await order.save(); diff --git a/bot/ordersActions.ts b/bot/ordersActions.ts index 66dcbdac..aac28c00 100644 --- a/bot/ordersActions.ts +++ b/bot/ordersActions.ts @@ -297,7 +297,7 @@ const getOrder = async ( const where = { _id: orderId, - $or: [{ seller_id: user._id }, { buyer_id: user._id }], + $or: [{ seller_id: user._id.toString() }, { buyer_id: user._id.toString() }], }; const order = await Order.findOne(where).exec(); @@ -365,7 +365,7 @@ const getNewRangeOrderPayload = async (order: IOrder) => { paymentMethod: order.payment_method, status: 'PENDING', priceMargin: order.price_margin, - range_parent_id: order._id, + range_parent_id: order._id.toString(), tgChatId: order.tg_chat_id, tgOrderMessage: order.tg_order_message, community_id: order.community_id, diff --git a/bot/scenes.ts b/bot/scenes.ts index 6db6e7b2..80814fc2 100644 --- a/bot/scenes.ts +++ b/bot/scenes.ts @@ -145,8 +145,8 @@ const addInvoicePHIWizard = new Scenes.WizardScene( return await messages.incorrectAmountInvoiceMessage(ctx); const isScheduled = await PendingPayment.findOne({ - order_id: order._id, - attempts: { $lt: process.env.PAYMENT_ATTEMPTS }, + order_id: order._id.toString(), + attempts: { $lt: Number(process.env.PAYMENT_ATTEMPTS) }, is_invoice_expired: false, }); // We check if the payment is on flight diff --git a/bot/start.ts b/bot/start.ts index 53e2b8f9..6e7de4ca 100644 --- a/bot/start.ts +++ b/bot/start.ts @@ -3,7 +3,7 @@ import { Telegraf, session, Context, Telegram } from 'telegraf'; import { I18n, I18nContext } from '@grammyjs/i18n'; import { Message } from 'typegram'; import { UserDocument } from '../models/user'; -import { FilterQuery } from 'mongoose'; +import { QueryFilter } from 'mongoose'; import * as OrderEvents from './modules/events/orders'; import { limit } from '@grammyjs/ratelimiter'; import schedule from 'node-schedule'; @@ -99,9 +99,9 @@ const askForConfirmation = async (user: UserDocument, command: string) => { try { let orders: any[] = []; if (command === '/cancel') { - const where: FilterQuery = { + const where: QueryFilter = { $and: [ - { $or: [{ buyer_id: user._id }, { seller_id: user._id }] }, + { $or: [{ buyer_id: user._id.toString() }, { seller_id: user._id.toString() }] }, { $or: [ { status: 'ACTIVE' }, @@ -114,14 +114,14 @@ const askForConfirmation = async (user: UserDocument, command: string) => { }; orders = await Order.find(where); } else if (command === '/fiatsent') { - const where: FilterQuery = { - $and: [{ buyer_id: user._id }, { status: 'ACTIVE' }], + const where: QueryFilter = { + $and: [{ buyer_id: user._id.toString() }, { status: 'ACTIVE' }], }; orders = await Order.find(where); } else if (command === '/release') { - const where: FilterQuery = { + const where: QueryFilter = { $and: [ - { seller_id: user._id }, + { seller_id: user._id.toString() }, { $or: [ { status: 'ACTIVE' }, @@ -133,8 +133,8 @@ const askForConfirmation = async (user: UserDocument, command: string) => { }; orders = await Order.find(where); } else if (command === '/setinvoice') { - const where: FilterQuery = { - buyer_id: user._id, + const where: QueryFilter = { + buyer_id: user._id.toString(), status: { $in: ['PAID_HOLD_INVOICE', 'WAITING_BUYER_INVOICE'] }, }; @@ -352,7 +352,7 @@ const initialize = ( order.is_frozen = true; order.status = 'FROZEN'; - order.action_by = ctx.admin._id; + order.action_by = ctx.admin._id.toString(); await order.save(); if (order.secret) await settleHoldInvoice({ secret: order.secret }); @@ -380,7 +380,7 @@ const initialize = ( if (order === null) return; // We look for a dispute for this order - const dispute = await Dispute.findOne({ order_id: order._id }); + const dispute = await Dispute.findOne({ order_id: order._id.toString() }); // We check if this is a solver, the order must be from the same community if (!ctx.admin.admin) { @@ -400,7 +400,7 @@ const initialize = ( // We check if this dispute is from a community we validate that // the solver is running this command - if (dispute && dispute.solver_id != ctx.admin._id) { + if (dispute && dispute.solver_id != ctx.admin._id.toString()) { logger.debug( `cancelorder ${order._id}: @${ctx.admin.username} is not the solver of this dispute`, ); @@ -418,7 +418,7 @@ const initialize = ( logger.info(`order ${order._id}: cancelled by admin`); order.status = 'CANCELED_BY_ADMIN'; - order.canceled_by = ctx.admin._id; + order.canceled_by = ctx.admin._id.toString(); await order.save(); order.status = 'CANCELED'; OrderEvents.orderUpdated(order); @@ -530,7 +530,7 @@ const initialize = ( } // We look for a dispute for this order - const dispute = await Dispute.findOne({ order_id: order._id }); + const dispute = await Dispute.findOne({ order_id: order._id.toString() }); // We check if this is a solver, the order must be from the same community if (!ctx.admin.admin) { @@ -1015,8 +1015,8 @@ const initialize = ( // We make sure the buyers invoice is not being paid const isPending = await PendingPayment.findOne({ - order_id: order._id, - attempts: { $lt: process.env.PAYMENT_ATTEMPTS }, + order_id: order._id.toString(), + attempts: { $lt: Number(process.env.PAYMENT_ATTEMPTS) }, }); if (isPending) return; diff --git a/bot/validations.ts b/bot/validations.ts index 7ee42bbb..7509ab58 100644 --- a/bot/validations.ts +++ b/bot/validations.ts @@ -5,7 +5,7 @@ import { ctxUpdateAssertMsg, } from './start'; import { IUsernameId } from '../models/community'; -import { FilterQuery } from 'mongoose'; +import { QueryFilter } from 'mongoose'; import { UserDocument } from '../models/user'; import { IOrder } from '../models/order'; // @ts-ignore @@ -44,7 +44,7 @@ const validateUser = async (ctx: MainContext, start: boolean) => { return false; } - let user = await User.findOne({ tg_id: tgUser.id }); + let user = await User.findOne({ tg_id: String(tgUser.id) }); if (!user && start) { user = new User({ @@ -519,8 +519,8 @@ const validateReleaseOrder = async ( orderId: string, ) => { try { - let where: FilterQuery = { - seller_id: user._id, + let where: QueryFilter = { + seller_id: user._id.toString(), status: 'WAITING_BUYER_INVOICE', _id: orderId, }; @@ -532,7 +532,7 @@ const validateReleaseOrder = async ( where = { $and: [ - { seller_id: user._id }, + { seller_id: user._id.toString() }, { $or: [ { status: 'ACTIVE' }, @@ -570,7 +570,7 @@ const validateDisputeOrder = async ( $and: [ { _id: orderId }, { $or: [{ status: 'ACTIVE' }, { status: 'FIAT_SENT' }] }, - { $or: [{ seller_id: user._id }, { buyer_id: user._id }] }, + { $or: [{ seller_id: user._id.toString() }, { buyer_id: user._id.toString() }] }, ], }; @@ -594,9 +594,9 @@ const validateFiatSentOrder = async ( orderId: string, ) => { try { - const where: FilterQuery = { + const where: QueryFilter = { $and: [ - { buyer_id: user._id }, + { buyer_id: user._id.toString() }, { $or: [{ status: 'ACTIVE' }, { status: 'PAID_HOLD_INVOICE' }] }, ], }; @@ -631,7 +631,7 @@ const validateFiatSentOrder = async ( const validateSeller = async (ctx: MainContext, user: UserDocument) => { try { const where = { - seller_id: user._id, + seller_id: user._id.toString(), status: 'FIAT_SENT', }; @@ -697,8 +697,8 @@ const validateUserWaitingOrder = async ( ) => { try { // If is a seller - let where: FilterQuery = { - seller_id: user._id, + let where: QueryFilter = { + seller_id: user._id.toString(), status: 'WAITING_PAYMENT', }; let orders = await Order.find(where); @@ -708,7 +708,7 @@ const validateUserWaitingOrder = async ( } // If is a buyer where = { - buyer_id: user._id, + buyer_id: user._id.toString(), status: 'WAITING_BUYER_INVOICE', }; orders = await Order.find(where); @@ -733,7 +733,7 @@ const isBannedFromCommunity = async ( const community = await Community.findOne({ _id: communityId }); if (!community) return false; return community.banned_users.some( - (buser: IUsernameId) => buser.id == user._id, + (buser: IUsernameId) => buser.id == user._id.toString(), ); } catch (error) { logger.error(error); diff --git a/jobs/pending_payments.ts b/jobs/pending_payments.ts index 6a35dd18..ac6210e5 100644 --- a/jobs/pending_payments.ts +++ b/jobs/pending_payments.ts @@ -13,7 +13,7 @@ export const attemptPendingPayments = async ( ): Promise => { const pendingPayments = await PendingPayment.find({ paid: false, - attempts: { $lt: process.env.PAYMENT_ATTEMPTS }, + attempts: { $lt: Number(process.env.PAYMENT_ATTEMPTS) }, is_invoice_expired: false, community_id: null, next_retry: { $lte: new Date() }, @@ -162,7 +162,7 @@ export const attemptCommunitiesPendingPayments = async ( ): Promise => { const pendingPayments = await PendingPayment.find({ paid: false, - attempts: { $lt: process.env.PAYMENT_ATTEMPTS }, + attempts: { $lt: Number(process.env.PAYMENT_ATTEMPTS) }, is_invoice_expired: false, community_id: { $ne: null }, next_retry: { $lte: new Date() }, diff --git a/models/community.ts b/models/community.ts index 0434174e..4411fbea 100644 --- a/models/community.ts +++ b/models/community.ts @@ -35,7 +35,7 @@ const usernameIdSchema = new Schema({ }); export interface ICommunity extends Document { - _id: string; + id: string; name: string; creator_id: string; group: string; diff --git a/models/order.ts b/models/order.ts index 4cad7b51..04cba971 100644 --- a/models/order.ts +++ b/models/order.ts @@ -1,7 +1,7 @@ import mongoose, { Document, Schema } from 'mongoose'; export interface IOrder extends Document { - _id: string; + id: string; description?: string; amount: number; max_amount: number; diff --git a/models/user.ts b/models/user.ts index 699091c8..aeea799a 100644 --- a/models/user.ts +++ b/models/user.ts @@ -6,7 +6,7 @@ interface UserReview { } export interface UserDocument extends Document { - _id: string; + id: string; tg_id: string; username?: string; lang: string; diff --git a/package-lock.json b/package-lock.json index 13072753..16bbb165 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "dotenv": "^10.0.0", "invoices": "2.0.6", "lightning": "10.25.0", - "mongoose": "^8.17.1", + "mongoose": "^9.0.0", "node-schedule": "^2.0.0", "nostr-tools": "^2.5.2", "qrcode": "^1.5.0", @@ -331,9 +331,9 @@ } }, "node_modules/@mongodb-js/saslprep": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.0.tgz", - "integrity": "sha512-zlayKCsIjYb7/IdfqxorK5+xUMyi4vOKcFy10wKJYc63NSdKI8mNME+uJqfatkPmOSMMUiojrL58IePKBm3gvQ==", + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.11.tgz", + "integrity": "sha512-o9rAHc0IpIjuPSxRutWpE1F62x7n+4mVS4rCNHkzhIUMQcc18bb6xEq5wd2NdN0WjepIyXIppRshYI2kQDOZVA==", "license": "MIT", "dependencies": { "sparse-bitfield": "^3.0.3" @@ -684,9 +684,9 @@ "license": "MIT" }, "node_modules/@types/whatwg-url": { - "version": "11.0.5", - "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", - "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-13.0.0.tgz", + "integrity": "sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q==", "license": "MIT", "dependencies": { "@types/webidl-conversions": "*" @@ -1441,12 +1441,12 @@ } }, "node_modules/bson": { - "version": "6.10.4", - "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", - "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-7.2.0.tgz", + "integrity": "sha512-YCEo7KjMlbNlyHhz7zAZNDpIpQbd+wOEHJYezv0nMYTn4x31eIUM2yomNNubclAt63dObUzKHWsBLJ9QcZNSnQ==", "license": "Apache-2.0", "engines": { - "node": ">=16.20.1" + "node": ">=20.19.0" } }, "node_modules/buffer": { @@ -3695,13 +3695,10 @@ } }, "node_modules/ip-address": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", - "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", - "dependencies": { - "jsbn": "1.1.0", - "sprintf-js": "^1.1.3" - }, + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "license": "MIT", "engines": { "node": ">= 12" } @@ -4080,11 +4077,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jsbn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", - "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" - }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -4122,12 +4114,12 @@ "dev": true }, "node_modules/kareem": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", - "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-3.3.0.tgz", + "integrity": "sha512-kpSuLD3/7RenBnjnJdOHXCKC8dTd1JzeOiJhN0necWWci6cC+qX+VuwPnMVgb+a4+KNJSfgqahpnfWaeDXCimw==", "license": "Apache-2.0", "engines": { - "node": ">=12.0.0" + "node": ">=18.0.0" } }, "node_modules/keyv": { @@ -4730,26 +4722,26 @@ "dev": true }, "node_modules/mongodb": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.18.0.tgz", - "integrity": "sha512-fO5ttN9VC8P0F5fqtQmclAkgXZxbIkYRTUi1j8JO6IYwvamkhtYDilJr35jOPELR49zqCJgXZWwCtW7B+TM8vQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.2.0.tgz", + "integrity": "sha512-F/2+BMZtLVhY30ioZp0dAmZ+IRZMBqI+nrv6t5+9/1AIwCa8sMRC3jBf81lpxMhnZgqq8CoUD503Z1oZWq1/sw==", "license": "Apache-2.0", "dependencies": { - "@mongodb-js/saslprep": "^1.1.9", - "bson": "^6.10.4", - "mongodb-connection-string-url": "^3.0.0" + "@mongodb-js/saslprep": "^1.3.0", + "bson": "^7.2.0", + "mongodb-connection-string-url": "^7.0.0" }, "engines": { - "node": ">=16.20.1" + "node": ">=20.19.0" }, "peerDependencies": { - "@aws-sdk/credential-providers": "^3.188.0", - "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", - "gcp-metadata": "^5.2.0", - "kerberos": "^2.0.1", - "mongodb-client-encryption": ">=6.0.0 <7", - "snappy": "^7.2.2", - "socks": "^2.7.1" + "@aws-sdk/credential-providers": "^3.806.0", + "@mongodb-js/zstd": "^7.0.0", + "gcp-metadata": "^7.0.1", + "kerberos": "^7.0.0", + "mongodb-client-encryption": ">=7.0.0 <7.1.0", + "snappy": "^7.3.2", + "socks": "^2.8.6" }, "peerDependenciesMeta": { "@aws-sdk/credential-providers": { @@ -4776,31 +4768,33 @@ } }, "node_modules/mongodb-connection-string-url": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", - "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-7.0.1.tgz", + "integrity": "sha512-h0AZ9A7IDVwwHyMxmdMXKy+9oNlF0zFoahHiX3vQ8e3KFcSP3VmsmfvtRSuLPxmyv2vjIDxqty8smTgie/SNRQ==", "license": "Apache-2.0", "dependencies": { - "@types/whatwg-url": "^11.0.2", - "whatwg-url": "^14.1.0 || ^13.0.0" + "@types/whatwg-url": "^13.0.0", + "whatwg-url": "^14.1.0" + }, + "engines": { + "node": ">=20.19.0" } }, "node_modules/mongoose": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.17.1.tgz", - "integrity": "sha512-aodS4cacux5caoxB5ErEwRmrafIUsVRJxHnvP7URnSUnTenr32j1qBVV+KjYxryyLSisQkxglAFF69TNLeZTLg==", + "version": "9.6.2", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-9.6.2.tgz", + "integrity": "sha512-7m8HntjkoRnwEmuPC0kdlwcZXJOQf4twumFj+PNzg/anqqZE2Er7hQslqyzy07mP3JcFjoTSgH5765PyqOXsxw==", "license": "MIT", "dependencies": { - "bson": "^6.10.4", - "kareem": "2.6.3", - "mongodb": "~6.18.0", + "kareem": "3.3.0", + "mongodb": "~7.2", "mpath": "0.9.0", - "mquery": "5.0.0", + "mquery": "6.0.0", "ms": "2.1.3", "sift": "17.1.3" }, "engines": { - "node": ">=16.20.1" + "node": ">=20.19.0" }, "funding": { "type": "opencollective", @@ -4816,15 +4810,12 @@ } }, "node_modules/mquery": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", - "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-6.0.0.tgz", + "integrity": "sha512-b2KQNsmgtkscfeDgkYMcWGn9vZI9YoXh802VDEwE6qc50zxBFQ0Oo8ROkawbPAsXCY1/Z1yp0MagqsZStPWJjw==", "license": "MIT", - "dependencies": { - "debug": "4.x" - }, "engines": { - "node": ">=14.0.0" + "node": ">=20.19.0" } }, "node_modules/ms": { @@ -6424,11 +6415,12 @@ } }, "node_modules/socks": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", - "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.9.tgz", + "integrity": "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==", + "license": "MIT", "dependencies": { - "ip-address": "^9.0.5", + "ip-address": "^10.1.1", "smart-buffer": "^4.2.0" }, "engines": { @@ -6463,11 +6455,6 @@ "memory-pager": "^1.0.2" } }, - "node_modules/sprintf-js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" - }, "node_modules/stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", diff --git a/package.json b/package.json index 4683c48c..cfe2f8a5 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "dotenv": "^10.0.0", "invoices": "2.0.6", "lightning": "10.25.0", - "mongoose": "^8.17.1", + "mongoose": "^9.6.2", "node-schedule": "^2.0.0", "nostr-tools": "^2.5.2", "qrcode": "^1.5.0", diff --git a/tsconfig.json b/tsconfig.json index f1492da5..c37ea80d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { "strict": true, + "skipLibCheck": true, "esModuleInterop": true, "resolveJsonModule": true, "downlevelIteration": true, diff --git a/util/communityHelper.ts b/util/communityHelper.ts index 76a18e4c..5379bd6c 100644 --- a/util/communityHelper.ts +++ b/util/communityHelper.ts @@ -29,7 +29,7 @@ export const getCommunityInfo = async ( const regex = new RegExp(`^@${chat.username}$`, 'i'); community = await Community.findOne({ group: regex }); if (community) { - communityId = community._id; + communityId = community._id.toString(); } } else if (user.default_community_id) { // Private chat with default community diff --git a/util/index.ts b/util/index.ts index d484dec6..c484c24b 100644 --- a/util/index.ts +++ b/util/index.ts @@ -37,7 +37,7 @@ const isIso4217 = (code: string): boolean => { const isOrderCreator = (user: UserDocument, order: IOrder) => { try { - return user._id == order.creator_id; + return user._id.toString() == order.creator_id; } catch (error) { logger.error(error); return false; @@ -88,8 +88,8 @@ const handleReputationItems = async ( const yesterday = new Date(Date.now() - 86400000).toISOString(); const orders = await Order.find({ status: 'SUCCESS', - seller_id: buyer._id, - buyer_id: seller._id, + seller_id: buyer._id.toString(), + buyer_id: seller._id.toString(), taken_at: { $gte: yesterday }, }); if (orders.length > 0) { @@ -454,7 +454,7 @@ const isDisputeSolver = (community: ICommunity | null, user: UserDocument) => { return false; } - return community.solvers.some(solver => solver.id == user._id); + return community.solvers.some(solver => solver.id == user._id.toString()); }; // Return the fee the bot will charge to the seller From a0379d0579db6aeb7e366ae11bae0bd6847bb18d Mon Sep 17 00:00:00 2001 From: Tori Date: Wed, 13 May 2026 23:06:59 -0500 Subject: [PATCH 02/12] =?UTF-8?q?fix:=20address=20CodeRabbit=20review=20?= =?UTF-8?q?=E2=80=94=20strict=20equality,=20consistent=20toString,=20Node?= =?UTF-8?q?=20engine?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot/commands.ts | 4 ++-- bot/ordersActions.ts | 2 +- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bot/commands.ts b/bot/commands.ts index 39b16afc..b2e04e7b 100644 --- a/bot/commands.ts +++ b/bot/commands.ts @@ -761,12 +761,12 @@ const cancelOrder = async ( if (updateOrder.hash) await cancelHoldInvoice({ hash: updateOrder.hash }); updateOrder.status = 'CANCELED'; - updateOrder.canceled_by = String(user._id); + updateOrder.canceled_by = user._id.toString(); await updateOrder.save(); let seller = initiatorUser; let i18nCtxSeller = ctx.i18n; - if (order.seller_id == counterPartyUser._id.toString()) { + if (order.seller_id === counterPartyUser._id.toString()) { seller = counterPartyUser; i18nCtxSeller = i18nCtxCP; } diff --git a/bot/ordersActions.ts b/bot/ordersActions.ts index aac28c00..87deed46 100644 --- a/bot/ordersActions.ts +++ b/bot/ordersActions.ts @@ -318,7 +318,7 @@ const getOrders = async (user: UserDocument, status?: string) => { const where: any = { $and: [ { - $or: [{ buyer_id: user._id }, { seller_id: user._id }], + $or: [{ buyer_id: user._id.toString() }, { seller_id: user._id.toString() }], }, ], }; diff --git a/package.json b/package.json index cfe2f8a5..76c04285 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "lnp2pbot", "version": "0.15.0", "engines": { - "node": ">=18.0.0" + "node": ">=20.19.0" }, "author": "Francisco Calderón ", "description": "P2P lightning network telegram bot", From e9011ec68d1d73114f910c6d946ad53e3a070a6d Mon Sep 17 00:00:00 2001 From: Tori Date: Thu, 14 May 2026 00:21:45 -0500 Subject: [PATCH 03/12] fix: upgrade CI to ubuntu:26.04 and run prettier --- .github/workflows/integrate.yaml | 4 ++-- bot/commands.ts | 5 ++++- bot/ordersActions.ts | 10 ++++++++-- bot/start.ts | 7 ++++++- bot/validations.ts | 7 ++++++- 5 files changed, 26 insertions(+), 7 deletions(-) diff --git a/.github/workflows/integrate.yaml b/.github/workflows/integrate.yaml index cf0e62f1..d3d30d1b 100644 --- a/.github/workflows/integrate.yaml +++ b/.github/workflows/integrate.yaml @@ -13,7 +13,7 @@ jobs: ci_to_main: runs-on: ubuntu-latest container: - image: 'ubuntu:24.04' + image: 'ubuntu:26.04' steps: - name: Update apt and install required packages run: | @@ -40,7 +40,7 @@ jobs: - name: Install MongoDB run: | - echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-6.0.gpg ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/6.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-6.0.list + echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-6.0.gpg ] https://repo.mongodb.org/apt/ubuntu noble/mongodb-org/6.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-6.0.list sudo apt-get update sudo apt-get install -y mongodb-org - name: Check Mongo version diff --git a/bot/commands.ts b/bot/commands.ts index b2e04e7b..87fe0a43 100644 --- a/bot/commands.ts +++ b/bot/commands.ts @@ -739,7 +739,10 @@ const cancelOrder = async ( if (counterPartyUser == null) throw new Error('counterPartyUser was not found'); - const updateOrder = await setCooperativeCancelFlag(order._id.toString(), initiator); + const updateOrder = await setCooperativeCancelFlag( + order._id.toString(), + initiator, + ); // If the call returns null, the flag was already set (or order is missing), // so we treat it as a duplicate request. diff --git a/bot/ordersActions.ts b/bot/ordersActions.ts index 87deed46..a8323c96 100644 --- a/bot/ordersActions.ts +++ b/bot/ordersActions.ts @@ -297,7 +297,10 @@ const getOrder = async ( const where = { _id: orderId, - $or: [{ seller_id: user._id.toString() }, { buyer_id: user._id.toString() }], + $or: [ + { seller_id: user._id.toString() }, + { buyer_id: user._id.toString() }, + ], }; const order = await Order.findOne(where).exec(); @@ -318,7 +321,10 @@ const getOrders = async (user: UserDocument, status?: string) => { const where: any = { $and: [ { - $or: [{ buyer_id: user._id.toString() }, { seller_id: user._id.toString() }], + $or: [ + { buyer_id: user._id.toString() }, + { seller_id: user._id.toString() }, + ], }, ], }; diff --git a/bot/start.ts b/bot/start.ts index 6e7de4ca..0310dbc3 100644 --- a/bot/start.ts +++ b/bot/start.ts @@ -101,7 +101,12 @@ const askForConfirmation = async (user: UserDocument, command: string) => { if (command === '/cancel') { const where: QueryFilter = { $and: [ - { $or: [{ buyer_id: user._id.toString() }, { seller_id: user._id.toString() }] }, + { + $or: [ + { buyer_id: user._id.toString() }, + { seller_id: user._id.toString() }, + ], + }, { $or: [ { status: 'ACTIVE' }, diff --git a/bot/validations.ts b/bot/validations.ts index 7509ab58..7bf068be 100644 --- a/bot/validations.ts +++ b/bot/validations.ts @@ -570,7 +570,12 @@ const validateDisputeOrder = async ( $and: [ { _id: orderId }, { $or: [{ status: 'ACTIVE' }, { status: 'FIAT_SENT' }] }, - { $or: [{ seller_id: user._id.toString() }, { buyer_id: user._id.toString() }] }, + { + $or: [ + { seller_id: user._id.toString() }, + { buyer_id: user._id.toString() }, + ], + }, ], }; From db77a26104eafa836837a95e1191f78b7c2a7c86 Mon Sep 17 00:00:00 2001 From: Tori Date: Thu, 14 May 2026 10:20:14 -0500 Subject: [PATCH 04/12] fix: guard PAYMENT_ATTEMPTS env var against undefined in pending payment queries Use parseInt with a fallback default instead of Number() to avoid NaN being passed to MongoDB $lt operators when the env var is not set. --- jobs/pending_payments.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/jobs/pending_payments.ts b/jobs/pending_payments.ts index ac6210e5..c9219bcf 100644 --- a/jobs/pending_payments.ts +++ b/jobs/pending_payments.ts @@ -8,12 +8,16 @@ import { getUserI18nContext } from '../util'; import { CommunityContext } from '../bot/modules/community/communityContext'; import { orderUpdated } from '../bot/modules/events/orders'; +const maxPaymentAttempts = process.env.PAYMENT_ATTEMPTS + ? parseInt(process.env.PAYMENT_ATTEMPTS, 10) + : 3; + export const attemptPendingPayments = async ( bot: Telegraf, ): Promise => { const pendingPayments = await PendingPayment.find({ paid: false, - attempts: { $lt: Number(process.env.PAYMENT_ATTEMPTS) }, + attempts: { $lt: maxPaymentAttempts }, is_invoice_expired: false, community_id: null, next_retry: { $lte: new Date() }, @@ -162,7 +166,7 @@ export const attemptCommunitiesPendingPayments = async ( ): Promise => { const pendingPayments = await PendingPayment.find({ paid: false, - attempts: { $lt: Number(process.env.PAYMENT_ATTEMPTS) }, + attempts: { $lt: maxPaymentAttempts }, is_invoice_expired: false, community_id: { $ne: null }, next_retry: { $lte: new Date() }, From f2a47dce45ce369245df8f649fd16aa237eb043e Mon Sep 17 00:00:00 2001 From: Tori Date: Sun, 17 May 2026 17:24:45 -0500 Subject: [PATCH 05/12] fix: strict equality in banned_users check and downgrade CI to ubuntu:24.04 --- .github/workflows/integrate.yaml | 2 +- bot/validations.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/integrate.yaml b/.github/workflows/integrate.yaml index d3d30d1b..59d8bb8d 100644 --- a/.github/workflows/integrate.yaml +++ b/.github/workflows/integrate.yaml @@ -13,7 +13,7 @@ jobs: ci_to_main: runs-on: ubuntu-latest container: - image: 'ubuntu:26.04' + image: 'ubuntu:24.04' steps: - name: Update apt and install required packages run: | diff --git a/bot/validations.ts b/bot/validations.ts index 7bf068be..fdf50cce 100644 --- a/bot/validations.ts +++ b/bot/validations.ts @@ -738,7 +738,7 @@ const isBannedFromCommunity = async ( const community = await Community.findOne({ _id: communityId }); if (!community) return false; return community.banned_users.some( - (buser: IUsernameId) => buser.id == user._id.toString(), + (buser: IUsernameId) => buser.id === user._id.toString(), ); } catch (error) { logger.error(error); From 226cedda55105f9f35f90978c0abbcc9d9e121f3 Mon Sep 17 00:00:00 2001 From: Tori Date: Mon, 18 May 2026 14:24:14 -0500 Subject: [PATCH 06/12] fix: resolve TypeScript errors, CI compatibility, and test infrastructure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ci: upgrade container to ubuntu:26.04 (Node.js 20+) and MongoDB 8.0 (6.0 EOL Aug 2025, no ubuntu:26.04 repo; 8.0 noble repo compatible) - fix: add explicit _id: Types.ObjectId to IOrder, UserDocument, ICommunity so mongoose 9 does not leave _id as unknown (resolved ~70 TS errors) - fix: revert QueryFilter to FilterQuery — QueryFilter does not exist in mongoose 9.6.2, FilterQuery is still the correct named export - fix: convert block/index.ts require() calls to static imports so tsc compiles commands.ts and messages.ts transitively - fix: add monitoring.ts to tsconfig includes; add types: ["mocha"] to tsconfig.test.json to fix mocha globals in test compilation - fix: stub env vars as strings in validation.spec.ts to match Node.js process.env runtime behavior --- .github/workflows/integrate.yaml | 8 ++++---- bot/modules/block/index.ts | 6 +++--- bot/modules/community/commands.ts | 4 +++- bot/modules/dispute/actions.ts | 10 ++++++++-- bot/modules/dispute/commands.ts | 10 ++++++++-- bot/start.ts | 10 +++++----- bot/validations.ts | 8 ++++---- models/community.ts | 1 + models/order.ts | 3 ++- models/user.ts | 3 ++- tests/bot/validation.spec.ts | 4 ++-- tsconfig.json | 1 + tsconfig.test.json | 12 ++++++++++++ 13 files changed, 55 insertions(+), 25 deletions(-) diff --git a/.github/workflows/integrate.yaml b/.github/workflows/integrate.yaml index 59d8bb8d..27999e6a 100644 --- a/.github/workflows/integrate.yaml +++ b/.github/workflows/integrate.yaml @@ -13,7 +13,7 @@ jobs: ci_to_main: runs-on: ubuntu-latest container: - image: 'ubuntu:24.04' + image: 'ubuntu:26.04' steps: - name: Update apt and install required packages run: | @@ -34,13 +34,13 @@ jobs: sudo rm -f /etc/ssl/certs/ca-bundle.crt sudo apt reinstall --yes ca-certificates sudo update-ca-certificates - curl -fsSL https://pgp.mongodb.com/server-6.0.asc | \ - sudo gpg -o /usr/share/keyrings/mongodb-server-6.0.gpg \ + curl -fsSL https://pgp.mongodb.com/server-8.0.asc | \ + sudo gpg -o /usr/share/keyrings/mongodb-server-8.0.gpg \ --dearmor - name: Install MongoDB run: | - echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-6.0.gpg ] https://repo.mongodb.org/apt/ubuntu noble/mongodb-org/6.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-6.0.list + echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-8.0.gpg ] https://repo.mongodb.org/apt/ubuntu noble/mongodb-org/8.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-8.0.list sudo apt-get update sudo apt-get install -y mongodb-org - name: Check Mongo version diff --git a/bot/modules/block/index.ts b/bot/modules/block/index.ts index 5d7699ab..6e69a8ab 100644 --- a/bot/modules/block/index.ts +++ b/bot/modules/block/index.ts @@ -2,9 +2,9 @@ import { Telegraf } from 'telegraf'; import { CommunityContext } from '../community/communityContext'; import { logger } from '../../../logger'; -const commands = require('./commands'); -const messages = require('./messages'); -const { userMiddleware } = require('../../middleware/user'); +import * as commands from './commands'; +import * as messages from './messages'; +import { userMiddleware } from '../../middleware/user'; export const configure = (bot: Telegraf) => { bot.command('block', userMiddleware, async ctx => { diff --git a/bot/modules/community/commands.ts b/bot/modules/community/commands.ts index 1c95e938..b6f131e2 100644 --- a/bot/modules/community/commands.ts +++ b/bot/modules/community/commands.ts @@ -89,7 +89,9 @@ export const myComms = async (ctx: MainContext) => { try { const { user } = ctx; - const communities = await Community.find({ creator_id: user._id.toString() }); + const communities = await Community.find({ + creator_id: user._id.toString(), + }); if (!communities.length) return await ctx.reply(ctx.i18n.t('you_dont_have_communities')); diff --git a/bot/modules/dispute/actions.ts b/bot/modules/dispute/actions.ts index 69825d08..5a1b2988 100644 --- a/bot/modules/dispute/actions.ts +++ b/bot/modules/dispute/actions.ts @@ -35,10 +35,16 @@ export const takeDispute = async (ctx: MainContext): Promise => { if (seller === null) throw new Error('seller not found'); const initiator = order.buyer_dispute ? 'buyer' : 'seller'; const buyerDisputes = await Dispute.countDocuments({ - $or: [{ buyer_id: buyer._id.toString() }, { seller_id: buyer._id.toString() }], + $or: [ + { buyer_id: buyer._id.toString() }, + { seller_id: buyer._id.toString() }, + ], }); const sellerDisputes = await Dispute.countDocuments({ - $or: [{ buyer_id: seller._id.toString() }, { seller_id: seller._id.toString() }], + $or: [ + { buyer_id: seller._id.toString() }, + { seller_id: seller._id.toString() }, + ], }); dispute.solver_id = solver.id; diff --git a/bot/modules/dispute/commands.ts b/bot/modules/dispute/commands.ts index 42805bff..5b2d0855 100644 --- a/bot/modules/dispute/commands.ts +++ b/bot/modules/dispute/commands.ts @@ -53,11 +53,17 @@ export const handleDispute = async (ctx: MainContext, orderId: string) => { // If a user disputes is equal to MAX_DISPUTES, we ban the user const buyerDisputes = (await Dispute.countDocuments({ - $or: [{ buyer_id: buyer._id.toString() }, { seller_id: buyer._id.toString() }], + $or: [ + { buyer_id: buyer._id.toString() }, + { seller_id: buyer._id.toString() }, + ], })) + 1; const sellerDisputes = (await Dispute.countDocuments({ - $or: [{ buyer_id: seller._id.toString() }, { seller_id: seller._id.toString() }], + $or: [ + { buyer_id: seller._id.toString() }, + { seller_id: seller._id.toString() }, + ], })) + 1; const maxDisputes = Number(process.env.MAX_DISPUTES); // if MAX_DISPUTES is not specified or can't be parsed as number, following diff --git a/bot/start.ts b/bot/start.ts index 0310dbc3..d615f610 100644 --- a/bot/start.ts +++ b/bot/start.ts @@ -3,7 +3,7 @@ import { Telegraf, session, Context, Telegram } from 'telegraf'; import { I18n, I18nContext } from '@grammyjs/i18n'; import { Message } from 'typegram'; import { UserDocument } from '../models/user'; -import { QueryFilter } from 'mongoose'; +import type { FilterQuery } from 'mongoose'; import * as OrderEvents from './modules/events/orders'; import { limit } from '@grammyjs/ratelimiter'; import schedule from 'node-schedule'; @@ -99,7 +99,7 @@ const askForConfirmation = async (user: UserDocument, command: string) => { try { let orders: any[] = []; if (command === '/cancel') { - const where: QueryFilter = { + const where: FilterQuery = { $and: [ { $or: [ @@ -119,12 +119,12 @@ const askForConfirmation = async (user: UserDocument, command: string) => { }; orders = await Order.find(where); } else if (command === '/fiatsent') { - const where: QueryFilter = { + const where: FilterQuery = { $and: [{ buyer_id: user._id.toString() }, { status: 'ACTIVE' }], }; orders = await Order.find(where); } else if (command === '/release') { - const where: QueryFilter = { + const where: FilterQuery = { $and: [ { seller_id: user._id.toString() }, { @@ -138,7 +138,7 @@ const askForConfirmation = async (user: UserDocument, command: string) => { }; orders = await Order.find(where); } else if (command === '/setinvoice') { - const where: QueryFilter = { + const where: FilterQuery = { buyer_id: user._id.toString(), status: { $in: ['PAID_HOLD_INVOICE', 'WAITING_BUYER_INVOICE'] }, }; diff --git a/bot/validations.ts b/bot/validations.ts index fdf50cce..b3032cf7 100644 --- a/bot/validations.ts +++ b/bot/validations.ts @@ -5,7 +5,7 @@ import { ctxUpdateAssertMsg, } from './start'; import { IUsernameId } from '../models/community'; -import { QueryFilter } from 'mongoose'; +import type { FilterQuery } from 'mongoose'; import { UserDocument } from '../models/user'; import { IOrder } from '../models/order'; // @ts-ignore @@ -519,7 +519,7 @@ const validateReleaseOrder = async ( orderId: string, ) => { try { - let where: QueryFilter = { + let where: FilterQuery = { seller_id: user._id.toString(), status: 'WAITING_BUYER_INVOICE', _id: orderId, @@ -599,7 +599,7 @@ const validateFiatSentOrder = async ( orderId: string, ) => { try { - const where: QueryFilter = { + const where: FilterQuery = { $and: [ { buyer_id: user._id.toString() }, { $or: [{ status: 'ACTIVE' }, { status: 'PAID_HOLD_INVOICE' }] }, @@ -702,7 +702,7 @@ const validateUserWaitingOrder = async ( ) => { try { // If is a seller - let where: QueryFilter = { + let where: FilterQuery = { seller_id: user._id.toString(), status: 'WAITING_PAYMENT', }; diff --git a/models/community.ts b/models/community.ts index 4411fbea..fca1bffe 100644 --- a/models/community.ts +++ b/models/community.ts @@ -35,6 +35,7 @@ const usernameIdSchema = new Schema({ }); export interface ICommunity extends Document { + _id: Types.ObjectId; id: string; name: string; creator_id: string; diff --git a/models/order.ts b/models/order.ts index 04cba971..264ea599 100644 --- a/models/order.ts +++ b/models/order.ts @@ -1,6 +1,7 @@ -import mongoose, { Document, Schema } from 'mongoose'; +import mongoose, { Document, Schema, Types } from 'mongoose'; export interface IOrder extends Document { + _id: Types.ObjectId; id: string; description?: string; amount: number; diff --git a/models/user.ts b/models/user.ts index aeea799a..89f15d0c 100644 --- a/models/user.ts +++ b/models/user.ts @@ -1,4 +1,4 @@ -import mongoose, { Document, Schema } from 'mongoose'; +import mongoose, { Document, Schema, Types } from 'mongoose'; interface UserReview { rating: number; @@ -6,6 +6,7 @@ interface UserReview { } export interface UserDocument extends Document { + _id: Types.ObjectId; id: string; tg_id: string; username?: string; diff --git a/tests/bot/validation.spec.ts b/tests/bot/validation.spec.ts index 63a5f3a6..55622d49 100644 --- a/tests/bot/validation.spec.ts +++ b/tests/bot/validation.spec.ts @@ -67,9 +67,9 @@ describe('Validations', () => { sandbox = sinon.createSandbox(); // Mock process.env within the sandbox sandbox.stub(process, 'env').value({ - MIN_PAYMENT_AMT: 100, + MIN_PAYMENT_AMT: '100', NODE_ENV: 'production', - INVOICE_EXPIRATION_WINDOW: 3600000, + INVOICE_EXPIRATION_WINDOW: '3600000', }); replyStub = sinon.stub(ctx, 'reply'); diff --git a/tsconfig.json b/tsconfig.json index c37ea80d..0488ecf0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,6 +15,7 @@ }, "include": [ "app.ts", + "monitoring.ts", "bot/**/*", "jobs/**/*", "ln/**/*", diff --git a/tsconfig.test.json b/tsconfig.test.json index a657e5b4..cbec45b7 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -1,6 +1,18 @@ { "extends": "./tsconfig.json", + "compilerOptions": { + "types": ["mocha"] + }, "include": [ + "app.ts", + "monitoring.ts", + "bot/**/*", + "jobs/**/*", + "ln/**/*", + "lnurl/**/*", + "models/**/*", + "util/**/*", + "scripts/**/*", "tests/**/*" ], "exclude": [ From 8a1515850543b639a909d0aa442a82b1ee240d1a Mon Sep 17 00:00:00 2001 From: Tori Date: Mon, 18 May 2026 17:38:43 -0500 Subject: [PATCH 07/12] ci: install canvas build dependencies for ubuntu:26.04 ubuntu:26.04 ships Node.js 22.x which has no prebuilt canvas binaries, requiring compilation from source. Install pkg-config, libcairo2-dev, libpango1.0-dev, libjpeg-dev, libgif-dev and librsvg2-dev so node-gyp can build canvas successfully. --- .github/workflows/integrate.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/integrate.yaml b/.github/workflows/integrate.yaml index 27999e6a..8ecebaa1 100644 --- a/.github/workflows/integrate.yaml +++ b/.github/workflows/integrate.yaml @@ -23,6 +23,12 @@ jobs: sudo apt install --yes --no-install-recommends npm sudo apt install --yes git + + # Dependencies required to compile the canvas package from source + sudo apt install --yes --no-install-recommends \ + build-essential pkg-config libcairo2-dev libpango1.0-dev \ + libjpeg-dev libgif-dev librsvg2-dev + # one time fix to avoid permission problems later on git config --global --add safe.directory "$GITHUB_WORKSPACE" From 161692edfe333256a6f5e65589840aebc774f3f5 Mon Sep 17 00:00:00 2001 From: Tori Date: Mon, 18 May 2026 17:46:14 -0500 Subject: [PATCH 08/12] ci: switch to ubuntu:24.04 + NodeSource Node.js 20 for MongoDB 8.0 compatibility MongoDB 8.0 officially supports ubuntu 20.04/22.04/24.04 but not 26.04. Use ubuntu:24.04 with explicit Node.js 20.x via NodeSource to satisfy both MongoDB compatibility requirements and Node.js 20+ requirement. --- .github/workflows/integrate.yaml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/integrate.yaml b/.github/workflows/integrate.yaml index 8ecebaa1..b531db2c 100644 --- a/.github/workflows/integrate.yaml +++ b/.github/workflows/integrate.yaml @@ -13,14 +13,16 @@ jobs: ci_to_main: runs-on: ubuntu-latest container: - image: 'ubuntu:26.04' + image: 'ubuntu:24.04' steps: - name: Update apt and install required packages run: | apt update --yes - apt install --yes sudo + apt install --yes sudo curl - sudo apt install --yes --no-install-recommends npm + # Install Node.js 20.x via NodeSource (ubuntu:24.04 ships Node 18 by default) + curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - + sudo apt install --yes nodejs sudo apt install --yes git From 26b263f21ed7116ab1f2ea8457ed27197fd90987 Mon Sep 17 00:00:00 2001 From: Tori Date: Mon, 18 May 2026 18:16:17 -0500 Subject: [PATCH 09/12] fix: replace FilterQuery with QueryFilter import for mongoose 9 compatibility FilterQuery was removed in mongoose 9 and replaced with QueryFilter. Import QueryFilter aliased as FilterQuery to avoid touching all usages. --- bot/start.ts | 2 +- bot/validations.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/start.ts b/bot/start.ts index d615f610..ec6e862a 100644 --- a/bot/start.ts +++ b/bot/start.ts @@ -3,7 +3,7 @@ import { Telegraf, session, Context, Telegram } from 'telegraf'; import { I18n, I18nContext } from '@grammyjs/i18n'; import { Message } from 'typegram'; import { UserDocument } from '../models/user'; -import type { FilterQuery } from 'mongoose'; +import type { QueryFilter as FilterQuery } from 'mongoose'; import * as OrderEvents from './modules/events/orders'; import { limit } from '@grammyjs/ratelimiter'; import schedule from 'node-schedule'; diff --git a/bot/validations.ts b/bot/validations.ts index b3032cf7..6bab6a6b 100644 --- a/bot/validations.ts +++ b/bot/validations.ts @@ -5,7 +5,7 @@ import { ctxUpdateAssertMsg, } from './start'; import { IUsernameId } from '../models/community'; -import type { FilterQuery } from 'mongoose'; +import type { QueryFilter as FilterQuery } from 'mongoose'; import { UserDocument } from '../models/user'; import { IOrder } from '../models/order'; // @ts-ignore From fd69e040e3cde4116ad0d8d1c01580af1025ef78 Mon Sep 17 00:00:00 2001 From: Tori Date: Mon, 18 May 2026 18:39:05 -0500 Subject: [PATCH 10/12] fix: sync package-lock.json root metadata with package.json Update mongoose range and node engine fields in the lock file root package entry to match package.json, preventing npm install from modifying them during CI and breaking the git diff check. --- package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 16bbb165..2bb1dc43 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "dotenv": "^10.0.0", "invoices": "2.0.6", "lightning": "10.25.0", - "mongoose": "^9.0.0", + "mongoose": "^9.6.2", "node-schedule": "^2.0.0", "nostr-tools": "^2.5.2", "qrcode": "^1.5.0", @@ -53,7 +53,7 @@ "typescript": "5.1.6" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.19.0" } }, "node_modules/@colors/colors": { From 37ac635e79f9638ef2083d342b3045c420f0852e Mon Sep 17 00:00:00 2001 From: Tori Date: Mon, 18 May 2026 19:37:06 -0500 Subject: [PATCH 11/12] ci: switch back to ubuntu:26.04 with Node.js 22 and MongoDB 8.0 noble repo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ubuntu:26.04 ships Node.js 22 natively — no NodeSource install needed. MongoDB 8.0 noble (24.04) packages install and run correctly on 26.04, confirmed locally via Docker. Removes the NodeSource workaround added when we mistakenly downgraded to ubuntu:24.04. --- .github/workflows/integrate.yaml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/integrate.yaml b/.github/workflows/integrate.yaml index b531db2c..d927c8c4 100644 --- a/.github/workflows/integrate.yaml +++ b/.github/workflows/integrate.yaml @@ -13,18 +13,12 @@ jobs: ci_to_main: runs-on: ubuntu-latest container: - image: 'ubuntu:24.04' + image: 'ubuntu:26.04' steps: - name: Update apt and install required packages run: | apt update --yes - apt install --yes sudo curl - - # Install Node.js 20.x via NodeSource (ubuntu:24.04 ships Node 18 by default) - curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - - sudo apt install --yes nodejs - - sudo apt install --yes git + apt install --yes sudo curl git nodejs # Dependencies required to compile the canvas package from source sudo apt install --yes --no-install-recommends \ From a12e0572de5abe6f59959550881290aed9464fef Mon Sep 17 00:00:00 2001 From: Tori Date: Mon, 18 May 2026 19:46:35 -0500 Subject: [PATCH 12/12] =?UTF-8?q?ci:=20add=20npm=20to=20apt=20install=20?= =?UTF-8?q?=E2=80=94=20ubuntu:26.04=20ships=20nodejs=20and=20npm=20separat?= =?UTF-8?q?ely?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/integrate.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integrate.yaml b/.github/workflows/integrate.yaml index d927c8c4..a7dc2737 100644 --- a/.github/workflows/integrate.yaml +++ b/.github/workflows/integrate.yaml @@ -18,7 +18,7 @@ jobs: - name: Update apt and install required packages run: | apt update --yes - apt install --yes sudo curl git nodejs + apt install --yes sudo curl git nodejs npm # Dependencies required to compile the canvas package from source sudo apt install --yes --no-install-recommends \