Skip to content

feat: add /scheduleorder and /cancelschedule for recurring auto-republishing orders#849

Open
Matobi98 wants to merge 12 commits into
lnp2pBot:mainfrom
Matobi98:feat/scheduleorder
Open

feat: add /scheduleorder and /cancelschedule for recurring auto-republishing orders#849
Matobi98 wants to merge 12 commits into
lnp2pBot:mainfrom
Matobi98:feat/scheduleorder

Conversation

@Matobi98

@Matobi98 Matobi98 commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

feat: add /scheduleorder and /cancelschedule for recurring auto-republishing orders

Closes #769.

Adds /scheduleorder — creates an order that automatically republishes on a recurring schedule (e.g. every day, weekdays, or specific days at a chosen hour) — and /cancelschedule <schedule_id> to stop it.

Design

Following the maintainer feedback on the original PR (mutating an order's republish_count / status was too invasive and let an order migrate between states), this implementation introduces a dedicated ScheduledOrder model that acts as a mold:

  • The order's lifecycle is never mutated. Each publication is a brand-new Order with its own id.
  • The ScheduledOrder stores the trade config + recurrence (days, hour in UTC) + a republish_count cap + last_order_id (mold → order link).
  • models/order.ts is left completely untouched — no new fields on Order.

How it works

  • /scheduleorder <buy|sell> <sats> <fiat_amount> <fiat_code> <payment_method> [premium] parses the order with the existing buy/sell validators, then opens an interactive wizard (/exit to leave at any time):
    1. Days — inline buttons: 📅 Every day · 💼 Mon–Fri · 🌴 Weekend · ⚙️ Custom (custom accepts a comma-separated list of day names, accent/case tolerant).
    2. Hour — a number 0–23 (UTC).
    3. Confirmation — summary + ✅ / ❌.
  • A cron job (jobs/scheduled_orders.ts) runs hourly and, for each active schedule whose day/hour (UTC) match, publishes a fresh order from the mold and decrements republish_count. When the counter hits 0 the schedule deactivates itself (self-cleaning, as the issue requested). The cap defaults to REPUBLISH_ORDER_DAYS (10).
  • Reset on take — when a scheduled order is taken, the schedule's counter is reset to the top and a new order is immediately published from the mold, so the trader's offer stays live without manual recreation.
  • /cancelschedule <schedule_id> deactivates a schedule by its own id.

Implementation built as a WizardScene

The multi-step flow is a Scenes.WizardScene registered in stageMiddleware, exactly like the /buy and /sell order wizard. This means:

  • /exit works at any step (inherited from the generic scene commands).
  • State lives in the per-private-chat scene session with the standard 20-minute TTL — no global bot.on('text') / bot.on('callback_query') listeners, no cross-chat interference, no duplicated middleware.

⚠️ Note on order accumulation

Even though republish_count caps the total number of republications as the issue asked, scheduled orders do not pile up on the channel. Each published order is a normal order and expires after ORDER_PUBLISHED_EXPIRATION_WINDOW (23h by default), removed by the existing delete_published_orders job. Since 23h is shorter than any schedule interval (daily/weekly), at most one scheduled order is live at a time — a weekly order expires ~23h after publishing, days before the next one.

New config

REPUBLISH_ORDER_DAYS=10 # max republication cycles before a schedule self-deactivates

Testing

  • Test suite green against the existing baseline (updated the recurring-jobs count assertion for the new cron job).
  • /help updated with both commands; i18n strings added.

Summary by CodeRabbit

  • New Features
    • Added /scheduleorder to create recurring buy/sell schedules and /cancelschedule to stop them.
    • Interactive flow lets users pick preset days or enter custom days, then confirm a UTC hour; includes a confirmation summary before saving.
    • Recurring schedules are published automatically at the top of each hour (UTC).
  • Bug Fixes
    • Improved republishing reliability after a successful take, refreshing and preventing duplicate republishes.
  • Documentation
    • Expanded /help text and added schedule flow translations and prompts across multiple languages.
  • Tests
    • Updated recurring job scheduling expectations.

Matobi98 added 4 commits June 29, 2026 17:50
…rders

Creates a new ScheduledOrder model that acts as a mold: each cron trigger
publishes a brand-new Order with its own id. No order lifecycle is mutated.
Taking a scheduled order resets the cycle counter and immediately republishes
from the mold so the trader's offer stays live without manual intervention.
@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds scheduled-order creation, publishing, cancellation, refresh-on-take, bot wiring, and locale strings across the bot.

Changes

Scheduled Recurring Orders

Layer / File(s) Summary
Model and schedule utilities
models/scheduled_order.ts, models/index.ts, bot/modules/schedule/helpers.ts, bot/modules/schedule/messages.ts
Defines the scheduled-order schema and exports helpers for republish counts, day parsing, preset days, day labels, and schedule confirmation prompts.
Schedule command flow and wizard
bot/modules/schedule/commands.ts, bot/modules/schedule/scenes.ts, bot/modules/schedule/index.ts
Adds /scheduleorder and /cancelschedule, wires the schedule wizard into the module, and implements the 4-step scheduling scene.
Publishing and refresh after take
jobs/scheduled_orders.ts, jobs/index.ts, bot/modules/orders/takeOrder.ts
Publishes matching schedules hourly, reserves republish cycles atomically, and refreshes a scheduled order after a take completes.
Bot module and cron wiring
bot/start.ts, bot/middleware/stage.ts, tests/bot/bot.spec.ts
Registers the schedule wizard and module, schedules the hourly job, and updates the job-count test.
Locale strings
locales/*.yaml
Adds schedule help text and schedule-flow translations in all updated locales.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

  • lnp2pBot/bot#630: Also changes the takebuy/takesell order-taking path in bot/modules/orders/takeOrder.ts.
  • lnp2pBot/bot#633: Touches the same takebuy/takesell functions in bot/modules/orders/takeOrder.ts.
  • lnp2pBot/bot#801: Affects publishBuyOrderMessage/publishSellOrderMessage, which this PR now calls from scheduled publishing and refresh flows.

Suggested reviewers: Catrya, grunch, mostronatorcoder

Poem

A rabbit hops by UTC light,
With schedules queued and set just right.
Days, hours, orders take their turn,
Then republish as cycles churn.
Hop hop!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Clear title matches the new scheduleorder/cancelschedule recurring order feature.
Linked Issues check ✅ Passed The PR implements recurring order scheduling, republish limits, take-time reset, and cancellation, covering the linked issue's core requirements.
Out of Scope Changes check ✅ Passed All changes support recurring scheduled orders, localization, wiring, or tests; no unrelated additions stand out.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 15

🧹 Nitpick comments (1)
tests/bot/bot.spec.ts (1)

466-466: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the scheduled-order cron explicitly.

This only checks the total number of jobs. The test still passes if some unrelated job is added while the publishScheduledOrders registration disappears or its cron expression changes.

🧪 Tighten the assertion
-    expect(scheduleStub.scheduleJob.callCount).to.be.equal(9);
+    expect(scheduleStub.scheduleJob.callCount).to.equal(9);
+    expect(
+      scheduleStub.scheduleJob
+        .getCalls()
+        .some(call => call.args[0] === '0 * * * *'),
+    ).to.equal(true);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/bot/bot.spec.ts` at line 466, The test currently only verifies the
total number of scheduled jobs, so it can miss regressions in the
`publishScheduledOrders` registration. Update the assertion in `bot.spec.ts` to
explicitly check the `scheduleJob` call for `publishScheduledOrders`, including
its cron expression and identifying callback/argument, so the test fails if that
schedule is removed or changed even when the overall call count stays the same.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@bot/modules/orders/takeOrder.ts`:
- Around line 98-100: The take flow in takeOrder.ts is being delayed by awaiting
scheduled-order republishing before sending the taker’s next-step message. In
the take-buy and take-sell paths around refreshScheduledOrder and
messages.beginTakeBuyMessage / messages.beginTakeSellMessage, start
refreshScheduledOrder in parallel without awaiting it, or move it after the
beginTake*Message call so Telegram/channel publish latency does not block the
active trade flow.
- Around line 200-204: The schedule update in takeOrder is happening
unconditionally after publishFn, even when the order was not actually published;
update the schedule only when the new order has been successfully published. Add
a guard in takeOrder around the schedule.last_order_id and
schedule.republish_count assignments, using the published state returned or set
by publishFn/newOrder before calling schedule.save. Keep the fix localized to
the publish-and-save flow in takeOrder so failed or skipped publishes do not
advance the schedule.

In `@bot/modules/schedule/commands.ts`:
- Around line 1-6: The schedule cancellation flow should validate scheduleId
before calling ScheduledOrder.findOne, since a non-ObjectId value can throw and
currently only gets logged without replying to the user. Add a guard using
Types.ObjectId.isValid(scheduleId) in the command path in commands.ts before the
query, and return an appropriate user-facing message when the id is invalid so
/cancelschedule cannot fail silently.

In `@bot/modules/schedule/helpers.ts`:
- Around line 8-58: parseCustomDays is currently hardcoded to English/Spanish
weekday aliases, so localized wizard prompts still reject valid user input in
other locales. Update the weekday parsing in helpers.ts by either making
DAY_ALIASES locale-aware (driven by the active locale or translated weekday
names) or switching the custom day input flow to a locale-neutral format, and
keep the parser logic in parseCustomDays aligned with whatever format the
schedule wizard expects.

In `@bot/modules/schedule/messages.ts`:
- Around line 3-15: formatDays() is hardcoding English weekday abbreviations, so
the confirmation summary is not localized. Update the DAY_LABELS mapping in
messages.ts to read weekday names from i18n/locales instead of static strings,
and make sure the caller still uses formatDays(days) unchanged. Keep the
localized labels aligned with the existing confirmation text flow and add/update
the matching entries under locales/ for each weekday.

In `@bot/modules/schedule/scenes.ts`:
- Around line 83-88: The schedule wizard hour parsing is too permissive because
scenes.ts uses parseInt on ctx.message text, which accepts inputs like 7pm,
12.5, and 0x10. Tighten the validation in the hour-handling flow so only a
strict integer string in the 0–23 range is accepted before setting state.hour,
and keep the invalid_hour reply path in the same scene logic.

In `@jobs/scheduled_orders.ts`:
- Around line 48-75: The scheduled order flow in the job handler should only
advance the schedule when a post is actually published successfully. Update the
logic around createOrder and the publishBuyOrderMessage/publishSellOrderMessage
call so you capture an explicit publish-success result, and only then set
last_order_id, decrement republish_count, and possibly deactivate the schedule.
Also reserve the current UTC slot before calling the publish helper, and make
sure a failed schedule.save() does not leave the order eligible to be published
again on the next run.

In `@locales/de.yaml`:
- Around line 264-265: The help text for the schedule order command is
translating the required buy|sell argument, which breaks the command handler’s
expected input. Update the localized usage strings in the entries for the
schedule-order command so the argument remains exactly buy|sell while keeping
the surrounding German text translated. Make the same correction in the
duplicate occurrence mentioned in the comment, and verify the command names and
placeholders in the help output still match what the wizard/parser expects.

In `@locales/es.yaml`:
- Around line 265-266: The Spanish command help text is using translated
placeholders for the first argument, but bot/modules/schedule/commands.ts only
accepts the literal values buy or sell, so update the schedule command syntax in
locales/es.yaml to keep buy|sell unchanged while leaving the rest of the
localized text intact. Make the same correction anywhere else in the Spanish
schedule help entries that mirror this syntax so users are shown the exact
values accepted by the command parser.

In `@locales/fa.yaml`:
- Line 317: The help text for the `/scheduleorder` command contains a malformed
optional premium token, so update the string in the locale entry to use a
properly closed optional bracketed token for the premium argument. Locate the
command usage text in the `fa.yaml` locale and correct the `[_پریمیوم_>`
fragment so the optional parameter syntax is valid and consistent with the other
placeholders.

In `@locales/fr.yaml`:
- Line 266: The `/scheduleorder` help text is using translated action tokens,
but the command handler only accepts the literal `buy|sell` values. Update the
relevant `/scheduleorder` entries in the locale definitions to document
`buy|sell` instead of `acheter|vendre`, keeping the rest of the argument
placeholders unchanged so the displayed usage matches the parser expected by the
schedule order handler.
- Around line 741-742: The weekday prompts in the French locale are using
examples that the schedule parser does not accept. Update the messages for
schedule_enter_custom_days and invalid_days in locales/fr.yaml to use weekday
aliases supported by bot/modules/schedule/helpers.ts, or add matching French
aliases in that parser so the examples and validation are aligned.

In `@locales/it.yaml`:
- Line 264: The localized `/scheduleorder` help text is using translated
arguments that do not match what `bot/modules/schedule/commands.ts` actually
parses. Update the `/scheduleorder` usage string in the locale entry to keep the
action argument as `buy|sell` (including the other affected locale occurrence)
so the help output matches the command’s accepted values and users can copy it
correctly.

In `@locales/pt.yaml`:
- Line 265: The `/scheduleorder` help text in the Portuguese locale is
advertising unsupported argument names, which makes the command docs
inconsistent with the parser. Update the localized string for the schedule order
entry in the locale file to use the same `buy|sell` arguments that the command
handler recognizes, and make the same correction in the other referenced
occurrence so the help text stays aligned with the actual parser.

In `@models/scheduled_order.ts`:
- Around line 3-5: The scheduled order typing still uses an `extends Document`
interface with `_id: string`, which should be updated consistently with the
other model typings to fix the Mongoose 8 TS2430 issue. Refactor
`IScheduledOrder` to avoid directly extending `Document`, and instead use the
same pattern applied in `models/user.ts`, `models/order.ts`, and
`models/community.ts`—either a plain interface paired with `HydratedDocument`,
or a `Document` generic approach that types `_id` consistently across all
models.

---

Nitpick comments:
In `@tests/bot/bot.spec.ts`:
- Line 466: The test currently only verifies the total number of scheduled jobs,
so it can miss regressions in the `publishScheduledOrders` registration. Update
the assertion in `bot.spec.ts` to explicitly check the `scheduleJob` call for
`publishScheduledOrders`, including its cron expression and identifying
callback/argument, so the test fails if that schedule is removed or changed even
when the overall call count stays the same.
🪄 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: ce3d73c9-7d9c-4d07-84ff-ec93c2b72026

📥 Commits

Reviewing files that changed from the base of the PR and between bf32684 and 2c56591.

📒 Files selected for processing (23)
  • bot/middleware/stage.ts
  • bot/modules/orders/takeOrder.ts
  • bot/modules/schedule/commands.ts
  • bot/modules/schedule/helpers.ts
  • bot/modules/schedule/index.ts
  • bot/modules/schedule/messages.ts
  • bot/modules/schedule/scenes.ts
  • bot/start.ts
  • jobs/index.ts
  • jobs/scheduled_orders.ts
  • locales/de.yaml
  • locales/en.yaml
  • locales/es.yaml
  • locales/fa.yaml
  • locales/fr.yaml
  • locales/it.yaml
  • locales/ko.yaml
  • locales/pt.yaml
  • locales/ru.yaml
  • locales/uk.yaml
  • models/index.ts
  • models/scheduled_order.ts
  • tests/bot/bot.spec.ts

Comment thread bot/modules/orders/takeOrder.ts Outdated
Comment thread bot/modules/orders/takeOrder.ts Outdated
Comment thread bot/modules/schedule/commands.ts
Comment thread bot/modules/schedule/helpers.ts Outdated
Comment on lines +8 to +58
// Day names (Spanish, English, abbreviated) mapped to getUTCDay() values (0=Sun)
const DAY_ALIASES: Record<string, number> = {
domingo: 0,
sunday: 0,
sun: 0,
dom: 0,
lunes: 1,
monday: 1,
mon: 1,
lun: 1,
martes: 2,
tuesday: 2,
tue: 2,
mar: 2,
miercoles: 3,
wednesday: 3,
wed: 3,
mie: 3,
jueves: 4,
thursday: 4,
thu: 4,
jue: 4,
viernes: 5,
friday: 5,
fri: 5,
vie: 5,
sabado: 6,
saturday: 6,
sat: 6,
sab: 6,
};

// Parses a comma/space separated list of day names into sorted weekday numbers.
// Tolerant to accents, casing and extra whitespace. Returns null if any token
// is not a recognized day.
export const parseCustomDays = (input: string): number[] | null => {
const parts = input
.toLowerCase()
.split(/[,\s]+/)
.filter(Boolean);
if (parts.length === 0) return null;
const days = new Set<number>();
for (const part of parts) {
// strip accents so "miércoles" -> "miercoles"
const normalized = part.normalize('NFD').replace(/[̀-ͯ]/g, '');
const dayNum = DAY_ALIASES[normalized];
if (dayNum === undefined) return null;
days.add(dayNum);
}
return [...days].sort((a, b) => a - b);
};

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.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Custom day parsing is locale-limited.

This wizard is being shipped with 10 locales, but parseCustomDays() only accepts English/Spanish weekday tokens. In fr, de, ru, etc., users can follow the localized prompt and still never get past invalid_days when they enter weekday names in their own language. Either localize the alias table per locale or change the custom input format to something locale-neutral. As per coding guidelines, "bot/**/*.ts: House commands, scenes, and middleware modules in bot/ directory; pair new flows with text updates under locales/".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bot/modules/schedule/helpers.ts` around lines 8 - 58, parseCustomDays is
currently hardcoded to English/Spanish weekday aliases, so localized wizard
prompts still reject valid user input in other locales. Update the weekday
parsing in helpers.ts by either making DAY_ALIASES locale-aware (driven by the
active locale or translated weekday names) or switching the custom day input
flow to a locale-neutral format, and keep the parser logic in parseCustomDays
aligned with whatever format the schedule wizard expects.

Source: Coding guidelines

Comment on lines +3 to +15
const DAY_LABELS: Record<number, string> = {
0: 'Sun',
1: 'Mon',
2: 'Tue',
3: 'Wed',
4: 'Thu',
5: 'Fri',
6: 'Sat',
};

export const formatDays = (days: number[]): string =>
days.map(d => DAY_LABELS[d]).join(', ');

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Localize the weekday labels in the confirmation summary.

formatDays() always renders Sun/Mon/… so every non-English locale gets an English day list inside an otherwise translated confirmation message. Pull these labels from i18n instead of hardcoding them here. As per coding guidelines, "bot/**/*.ts: House commands, scenes, and middleware modules in bot/ directory; pair new flows with text updates under locales/".

Also applies to: 65-70

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bot/modules/schedule/messages.ts` around lines 3 - 15, formatDays() is
hardcoding English weekday abbreviations, so the confirmation summary is not
localized. Update the DAY_LABELS mapping in messages.ts to read weekday names
from i18n/locales instead of static strings, and make sure the caller still uses
formatDays(days) unchanged. Keep the localized labels aligned with the existing
confirmation text flow and add/update the matching entries under locales/ for
each weekday.

Source: Coding guidelines

Comment thread locales/fr.yaml Outdated
Comment thread locales/fr.yaml Outdated
Comment thread locales/it.yaml Outdated
Comment thread locales/pt.yaml Outdated
Comment thread models/scheduled_order.ts
@Matobi98 Matobi98 marked this pull request as draft June 29, 2026 23:14

@ermeme ermeme Bot left a comment

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.

Code Review Summary

Verdict: Changes Requested

I re-read the PR conversation and the current head. The feature direction is good, but there are two blocking consistency bugs in the schedule refresh flow:

Blocking

  • bot/modules/orders/takeOrder.ts: refreshScheduledOrder() treats publishBuyOrderMessage() / publishSellOrderMessage() as if they return a success flag. They do not — those helpers swallow errors and return void, so if (!published) is always true. That means the schedule never updates its last_order_id or republish_count after a take, so the next taken order cannot be matched back to the schedule. In practice, the recurring-order feature does not refresh correctly.
  • jobs/scheduled_orders.ts: the hourly publisher advances the schedule after calling the publish helper, but there is no actual success signal and no atomic claim/check around the publish+save boundary. Because the publish helpers swallow failures, a Telegram/channel failure can still consume a republish cycle and move last_order_id, leaving the schedule desynced with the real published state. There is no reconciliation path here, so a transient failure can strand the schedule or make later refreshes behave incorrectly.

Why this is blocking

  • This is state that controls future publications. If it is advanced incorrectly, the feature can silently stop republishing or republish against the wrong state.
  • In a schedule-driven flow, I want the code to prove the publish succeeded before it mutates the schedule record.

If you want, I can re-check the next iteration after the publish/schedule handoff is made atomic or otherwise crash-safe.

Comment thread bot/modules/orders/takeOrder.ts Outdated
schedule.type === 'buy'
? publishBuyOrderMessage
: publishSellOrderMessage;
const published = await publishFn(bot, creator, newOrder, i18n, 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.

Blocking: publishBuyOrderMessage() / publishSellOrderMessage() return void, so published is always false here. The schedule never gets its last_order_id / republish_count updated after a take, which breaks the refresh flow.

Comment thread jobs/scheduled_orders.ts
schedule.type === 'buy'
? publishBuyOrderMessage
: publishSellOrderMessage;
await publishFn(bot, user, order, i18n, 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.

Blocking: this advances the schedule without a trustworthy success signal or atomic handoff. Since the publish helpers swallow failures, a send error can still consume a cycle and desync last_order_id from the actual published state.

@ermeme ermeme Bot left a comment

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.

Code Review Summary

Verdict: Request changes

Blocking

  • The new models/scheduled_order.ts interface still extends Document while declaring _id: string, which is a Mongoose 8 TS2430 compile blocker.
  • The publish/update path in jobs/scheduled_orders.ts (and the same refresh path in bot/modules/orders/takeOrder.ts) still advances the schedule only after the external publish. If schedule.save() fails after the order is already posted, the next tick can republish the same schedule or leave the refresh chain stranded.

Non-blocking

  • I did not find other blocking issues on the current head.

Comment thread models/scheduled_order.ts
@@ -0,0 +1,44 @@
import mongoose, { Document, Schema } from 'mongoose';

export interface IScheduledOrder extends Document {

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.

This is still a TS2430 blocker on Mongoose 8: Document already defines _id as ObjectId, so IScheduledOrder cannot narrow it to string. Please switch this model to the same typing pattern used elsewhere in the repo (plain interface + HydratedDocument, or a Document generic with a consistent _id type).

Comment thread jobs/scheduled_orders.ts Outdated
: publishSellOrderMessage;
await publishFn(bot, user, order, i18n, false);

// publishFn swallows errors and returns void, so we detect success from

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.

This publish flow is still crash-sensitive: publishBuyOrderMessage() / publishSellOrderMessage() can succeed on Telegram and then fail on schedule.save(). In that case the order is already live, but the schedule never advances, so the next hourly tick can publish a duplicate. Please make the publish + schedule update atomic or persist a reservation before the external side effect.

Comment thread bot/modules/orders/takeOrder.ts Outdated
: publishSellOrderMessage;
await publishFn(bot, creator, newOrder, i18n, false);

// publishFn swallows errors and returns void, so we detect success from the

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.

Same crash window here in the refresh path: if the refreshed order is published and schedule.save() fails, last_order_id / republish_count stay stale even though a new live order exists. That can strand the chain or let the hourly job republish again. Please fix this path with the same atomicity/retry guarantee as the cron job.

@Matobi98 Matobi98 marked this pull request as ready for review July 1, 2026 21:46

@coderabbitai coderabbitai Bot left a comment

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.

Caution

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

⚠️ Outside diff range comments (1)
jobs/scheduled_orders.ts (1)

48-93: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Reserve before creating the order, or clean up the loser.

createOrder() runs before the atomic claim. If reserved is null, Line 92 only skips publishing; the already-created order is left behind. Move the reservation before order creation, then set last_order_id after creation, or explicitly close/delete the unclaimed order.

Possible direction
-        const order = await ordersActions.createOrder(i18n, bot, user, {
-          type: schedule.type,
-          amount: schedule.amount,
-          fiatAmount: schedule.fiat_amount,
-          fiatCode: schedule.fiat_code,
-          paymentMethod: schedule.payment_method,
-          status: 'PENDING',
-          priceMargin: schedule.price_margin,
-          community_id: schedule.community_id,
-        });
-
-        if (!order) {
-          logger.warning(
-            `ScheduledOrder ${schedule._id}: failed to create order`,
-          );
-          continue;
-        }
-
         const remaining = schedule.republish_count - 1;
         const reserved = await ScheduledOrder.findOneAndUpdate(
           {
             _id: schedule._id,
@@
-          {
-            last_order_id: order._id,
-            republish_count: remaining,
-            active: remaining > 0,
-          },
+          { republish_count: remaining, active: remaining > 0 },
           { new: true },
         );
@@
           continue;
         }
+
+        const order = await ordersActions.createOrder(i18n, bot, user, {
+          type: schedule.type,
+          amount: schedule.amount,
+          fiatAmount: schedule.fiat_amount,
+          fiatCode: schedule.fiat_code,
+          paymentMethod: schedule.payment_method,
+          status: 'PENDING',
+          priceMargin: schedule.price_margin,
+          community_id: schedule.community_id,
+        });
+
+        if (!order) {
+          logger.warning(
+            `ScheduledOrder ${schedule._id}: failed to create order after reservation`,
+          );
+          continue;
+        }
+
+        await ScheduledOrder.updateOne(
+          { _id: reserved._id },
+          { last_order_id: order._id },
+        );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@jobs/scheduled_orders.ts` around lines 48 - 93, The ScheduledOrder processing
flow creates the order before claiming the cycle, so when
ScheduledOrder.findOneAndUpdate returns null the orphaned order is left behind.
Update the logic around ordersActions.createOrder and
ScheduledOrder.findOneAndUpdate so the cycle is reserved before creating the
order, or add cleanup for the loser by closing/deleting the created order when
the reservation fails. Use the existing ScheduledOrder and
ordersActions.createOrder flow to keep the reservation and order creation
consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@jobs/scheduled_orders.ts`:
- Around line 48-93: The ScheduledOrder processing flow creates the order before
claiming the cycle, so when ScheduledOrder.findOneAndUpdate returns null the
orphaned order is left behind. Update the logic around ordersActions.createOrder
and ScheduledOrder.findOneAndUpdate so the cycle is reserved before creating the
order, or add cleanup for the loser by closing/deleting the created order when
the reservation fails. Use the existing ScheduledOrder and
ordersActions.createOrder flow to keep the reservation and order creation
consistent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c3221794-0f60-4ed5-aa5e-3adb99f2a5db

📥 Commits

Reviewing files that changed from the base of the PR and between 2c56591 and b5e9792.

📒 Files selected for processing (17)
  • bot/modules/orders/takeOrder.ts
  • bot/modules/schedule/commands.ts
  • bot/modules/schedule/helpers.ts
  • bot/modules/schedule/messages.ts
  • bot/modules/schedule/scenes.ts
  • jobs/scheduled_orders.ts
  • locales/de.yaml
  • locales/en.yaml
  • locales/es.yaml
  • locales/fa.yaml
  • locales/fr.yaml
  • locales/it.yaml
  • locales/ko.yaml
  • locales/pt.yaml
  • locales/ru.yaml
  • locales/uk.yaml
  • models/scheduled_order.ts
💤 Files with no reviewable changes (1)
  • models/scheduled_order.ts
✅ Files skipped from review due to trivial changes (8)
  • locales/en.yaml
  • locales/de.yaml
  • locales/ko.yaml
  • locales/it.yaml
  • locales/ru.yaml
  • locales/pt.yaml
  • locales/fr.yaml
  • locales/es.yaml
🚧 Files skipped from review as they are similar to previous changes (6)
  • locales/uk.yaml
  • bot/modules/schedule/commands.ts
  • bot/modules/schedule/messages.ts
  • bot/modules/orders/takeOrder.ts
  • locales/fa.yaml
  • bot/modules/schedule/scenes.ts

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.

feat: Add /scheduleorder command for recurring/auto-republishing orders

1 participant