-
Notifications
You must be signed in to change notification settings - Fork 133
Upgrade mongoose version to 9.5.* #789
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| import mongoose from 'mongoose'; | ||
| import { validateFiatSentOrder, validateReleaseOrder } from './validations'; | ||
| import { | ||
| createHoldInvoice, | ||
|
|
@@ -24,6 +25,7 @@ import { IOrder } from '../models/order'; | |
| import { UserDocument } from '../models/user'; | ||
| import { HasTelegram, MainContext } from './start'; | ||
| import { CommunityContext } from './modules/community/communityContext'; | ||
| const { ObjectId } = mongoose.Types; | ||
|
|
||
| enum UserOrderRole { | ||
| BUYER, | ||
|
|
@@ -41,7 +43,7 @@ const setCooperativeCancelFlag = async ( | |
| const update = { [propName]: true }; | ||
| return await Order.findOneAndUpdate( | ||
| { | ||
| _id: orderId, | ||
| _id: new ObjectId(orderId), | ||
| [propName]: { $ne: true }, | ||
| status: { $in: ['ACTIVE', 'FIAT_SENT', 'DISPUTE'] }, | ||
| }, | ||
|
|
@@ -62,7 +64,7 @@ const waitPayment = async ( | |
| // If there is not fiat amount the function don't do anything | ||
| if (order.fiat_amount === undefined) { | ||
| logger.debug( | ||
| `waitPayment: fiat_amount === undefined, User Id ${ctx.user.id} order Id: ${order.id}`, | ||
| `waitPayment: fiat_amount === undefined, User Id ${ctx.user._id.toString()} order Id: ${order._id.toString()}`, | ||
| ); | ||
| return; | ||
| } | ||
|
|
@@ -87,7 +89,7 @@ const waitPayment = async ( | |
| // We create a hold invoice | ||
| const description = i18nCtx.t('hold_invoice_memo', { | ||
| botName: ctx.botInfo.username, | ||
| orderId: order._id, | ||
| orderId: order._id.toString(), | ||
| fiatCode: order.fiat_code, | ||
| fiatAmount: order.fiat_amount, | ||
| }); | ||
|
|
@@ -137,7 +139,7 @@ const addInvoice = async ( | |
| if (!order) { | ||
| 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) }); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 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 -C1Repository: lnp2pBot/bot Length of output: 8827 🏁 Script executed: fd -t f '\.ts$' -path '*/models/*' | head -20Repository: lnp2pBot/bot Length of output: 225 🏁 Script executed: rg -l "interface IOrder|class Order|model.*Order" --type=ts -m 5Repository: lnp2pBot/bot Length of output: 92 🏁 Script executed: # Find Order model definition
git ls-files | grep -i 'order' | grep -E 'models|schema' | head -10Repository: lnp2pBot/bot Length of output: 72 🏁 Script executed: cat -n models/order.tsRepository: lnp2pBot/bot Length of output: 6007 🏁 Script executed: rg -l "interface IUser|export.*User|model.*User" --type=ts models/ | head -5Repository: lnp2pBot/bot Length of output: 107 🏁 Script executed: cat -n models/user.tsRepository: 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}")
EOFRepository: lnp2pBot/bot Length of output: 126 🏁 Script executed: # Check package.json to see mongodb/mongoose versions
cat -n package.json | head -50Repository: lnp2pBot/bot Length of output: 1711 🏁 Script executed: # Search for any tests related to ObjectId or null handling
rg "ObjectId\(null\)" --type=tsRepository: 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 -60Repository: 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=tsRepository: 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 -80Repository: 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 -20Repository: 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.tsRepository: 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 -40Repository: 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 -50Repository: 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']}")
EOFRepository: 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 -10Repository: 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 -100Repository: 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.tsRepository: 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.tsRepository: 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=tsRepository: 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 -A1Repository: 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 -30Repository: 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])
EOFRepository: 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 -40Repository: 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=mdRepository: 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 -20Repository: lnp2pBot/bot Length of output: 167 🏁 Script executed: # Search for findOne patterns more carefully
rg "findOne.*_id" bot/validations.ts -A1 | head -30Repository: 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 -40Repository: 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 -60Repository: 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.tsRepository: 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.tsRepository: 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.tsRepository: 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 -A3Repository: 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 -B1Repository: 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.tsRepository: 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 -40Repository: lnp2pBot/bot Length of output: 578 Remove unnecessary The codebase has two patterns: Wrapping adds two problems:
Remove the Applies to: 142, 151, 180, 233, 236, 238, 305, 315, 324, 326, 418, 422, 512, 521, 523, 651, 656, 742, 745, 835 in 🤖 Prompt for AI Agents |
||
| if (!order) return; | ||
| } | ||
|
|
||
|
|
@@ -146,7 +148,7 @@ const addInvoice = async ( | |
| return; | ||
| } | ||
|
|
||
| const buyer = await User.findOne({ _id: order.buyer_id }); | ||
| const buyer = await User.findOne({ _id: new ObjectId(order.buyer_id!) }); | ||
| if (buyer === null) throw new Error('buyer was not found'); | ||
|
|
||
| if (order.fiat_amount === undefined) { | ||
|
|
@@ -175,7 +177,7 @@ const addInvoice = async ( | |
| return; | ||
| } | ||
| await order.save(); | ||
| const seller = await User.findOne({ _id: order.seller_id }); | ||
| const seller = await User.findOne({ _id: new ObjectId(order.seller_id!) }); | ||
| if (seller === null) throw new Error('seller was not found'); | ||
|
|
||
| if (buyer.lightning_address) { | ||
|
|
@@ -228,12 +230,12 @@ const rateUser = async ( | |
| const callerId = ctx.from?.id; | ||
|
|
||
| if (!orderId) return; | ||
| const order = await Order.findOne({ _id: orderId }); | ||
| const order = await Order.findOne({ _id: new ObjectId(orderId) }); | ||
|
|
||
| if (order === null) return; | ||
| const buyer = await User.findOne({ _id: order.buyer_id }); | ||
| const buyer = await User.findOne({ _id: new ObjectId(order.buyer_id!) }); | ||
| if (buyer === null) throw new Error('buyer was not found'); | ||
| const seller = await User.findOne({ _id: order.seller_id }); | ||
| const seller = await User.findOne({ _id: new ObjectId(order.seller_id!) }); | ||
| if (seller === null) throw new Error('seller was not found'); | ||
|
|
||
| let targetUser = buyer; | ||
|
|
@@ -300,7 +302,7 @@ const cancelAddInvoice = async ( | |
| const orderId = | ||
| !!ctx && (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) }); | ||
| } | ||
| } | ||
| if (order === null) return; | ||
|
|
@@ -310,7 +312,7 @@ const cancelAddInvoice = async ( | |
| await cancelHoldInvoice({ hash: order.hash }); | ||
| } | ||
|
|
||
| const user = await User.findOne({ _id: order.buyer_id }); | ||
| const user = await User.findOne({ _id: new ObjectId(order.buyer_id!) }); | ||
|
|
||
| if (user == null) return; | ||
|
|
||
|
|
@@ -319,9 +321,13 @@ const cancelAddInvoice = async ( | |
| if (order.status !== 'WAITING_BUYER_INVOICE') | ||
| return await messages.genericErrorMessage(ctx, user, i18nCtx); | ||
|
|
||
| const sellerUser = await User.findOne({ _id: order.seller_id }); | ||
| const sellerUser = await User.findOne({ | ||
| _id: new ObjectId(order.seller_id!), | ||
| }); | ||
| if (sellerUser === null) throw new Error('sellerUser was not found'); | ||
| const buyerUser = await User.findOne({ _id: order.buyer_id }); | ||
| const buyerUser = await User.findOne({ | ||
| _id: new ObjectId(order.buyer_id!), | ||
| }); | ||
| if (buyerUser === null) throw new Error('buyerUser was not found'); | ||
| const sellerTgId = sellerUser.tg_id; | ||
| // If order creator cancels it, it will not be republished | ||
|
|
@@ -413,11 +419,11 @@ const showHoldInvoice = async ( | |
| if (!order) { | ||
| 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) }); | ||
| if (order === null || order === undefined) return; | ||
| } | ||
|
|
||
| const user = await User.findOne({ _id: order.seller_id }); | ||
| const user = await User.findOne({ _id: new ObjectId(order.seller_id!) }); | ||
| if (!user) return; | ||
|
|
||
| if (order.status !== 'WAITING_PAYMENT') { | ||
|
|
@@ -507,7 +513,7 @@ const cancelShowHoldInvoice = async ( | |
| const orderId = | ||
| !!ctx && (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) }); | ||
| } | ||
| } | ||
| if (order === null) return; | ||
|
|
@@ -516,9 +522,13 @@ const cancelShowHoldInvoice = async ( | |
| if (order.hash) { | ||
| await cancelHoldInvoice({ hash: order.hash }); | ||
| } | ||
| const buyerUser = await User.findOne({ _id: order.buyer_id }); | ||
| const buyerUser = await User.findOne({ | ||
| _id: new ObjectId(order.buyer_id!), | ||
| }); | ||
| if (buyerUser === null) throw new Error('buyerUser was not found'); | ||
| const sellerUser = await User.findOne({ _id: order.seller_id }); | ||
| const sellerUser = await User.findOne({ | ||
| _id: new ObjectId(order.seller_id!), | ||
| }); | ||
| if (sellerUser === null) throw new Error('sellerUser was not found'); | ||
|
|
||
| const i18nCtxSeller = await getUserI18nContext(sellerUser); | ||
|
|
@@ -646,12 +656,12 @@ const addInvoicePHI = async ( | |
| ) => { | ||
| try { | ||
| ctx.deleteMessage(); | ||
| const order = await Order.findOne({ _id: orderId }); | ||
| const order = await Order.findOne({ _id: new ObjectId(orderId) }); | ||
| if (order === null) throw new Error('order was not found'); | ||
| // only orders with status PAID_HOLD_INVOICE are released payments | ||
| if (order.status !== 'PAID_HOLD_INVOICE') return; | ||
|
|
||
| const buyer = await User.findOne({ _id: order.buyer_id }); | ||
| const buyer = await User.findOne({ _id: new ObjectId(order.buyer_id!) }); | ||
| if (buyer === null) return; | ||
| if (order.amount === 0) { | ||
| await messages.genericErrorMessage(bot, buyer, ctx.i18n); | ||
|
|
@@ -680,7 +690,11 @@ const cancelOrder = async ( | |
| if (!user) return; | ||
| } | ||
| 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, | ||
| ); | ||
|
|
||
| if (!order) return; | ||
|
|
||
|
|
@@ -689,7 +703,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 | ||
|
|
@@ -737,16 +751,23 @@ const cancelOrder = async ( | |
|
|
||
| const initiatorUser = user; | ||
| if (initiatorUser._id.toString() === order.buyer_id?.toString()) { | ||
| counterPartyUser = await User.findOne({ _id: order.seller_id }); | ||
| counterPartyUser = await User.findOne({ | ||
| _id: new ObjectId(order.seller_id!), | ||
| }); | ||
| initiator = UserOrderRole.BUYER; | ||
| } else { | ||
| counterPartyUser = await User.findOne({ _id: order.buyer_id }); | ||
| counterPartyUser = await User.findOne({ | ||
| _id: new ObjectId(order.buyer_id!), | ||
| }); | ||
| initiator = UserOrderRole.SELLER; | ||
| } | ||
| if (counterPartyUser == null) | ||
| 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, | ||
| ); | ||
|
|
||
| // If the call returns null, the flag was already set (or order is missing), | ||
| // so we treat it as a duplicate request. | ||
|
|
@@ -768,12 +789,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) { | ||
| if (order.seller_id?.toString() == counterPartyUser._id.toString()) { | ||
| seller = counterPartyUser; | ||
| i18nCtxSeller = i18nCtxCP; | ||
| } | ||
|
|
@@ -830,7 +851,7 @@ const fiatSent = async ( | |
| if (!order) return; | ||
|
|
||
| order.status = 'FIAT_SENT'; | ||
| const seller = await User.findOne({ _id: order.seller_id }); | ||
| const seller = await User.findOne({ _id: new ObjectId(order.seller_id!) }); | ||
| if (seller === null) throw new Error('seller was not found'); | ||
| await order.save(); | ||
| // We sent messages to both parties | ||
|
|
@@ -868,7 +889,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'; | ||
|
|
@@ -900,7 +921,11 @@ const showQrCode = async ( | |
| if (!user) return; | ||
| } | ||
| 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, | ||
| ); | ||
|
|
||
| if (!order) return; | ||
| if (!order.hash) return; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix ESLint
import/firstfailure breaking CI.Pipeline reports:
ESLint (import/first): Import in body of module; reorder to top.Theconst { 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
📝 Committable suggestion
🧰 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