Conversation
WalkthroughReplaces Wasabi-specific Telegram token usage with a multi-bot, database-backed Telegram integration. Adds bot-scoped Telegram tables, repository, and types; removes Wasabi repository and schema. Updates runtime config/env variables, plugin boot gating, bot initialization to fetch token from DB, and API endpoint user lookup to be bot-id aware. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor Env as Env/Config
participant Plugin as Nitro Plugin (03.telegram)
participant Repo as Repository (DB)
participant BotSvc as Wasabi Bot Service
participant Bot as Telegram Bot
Env->>Plugin: Provide telegram.wasabiBotId, telegram.atriumBotId
alt Missing any botId
Plugin-->>Plugin: Return early (no startup)
else Both present
Plugin->>BotSvc: useCreateWasabiBot()
BotSvc->>Repo: findBot(wasabiBotId)
Repo-->>BotSvc: Bot(token, metadata)
alt Token missing
BotSvc-->>Plugin: Throw "Wasabi bot is not configured"
else Token present
BotSvc->>Bot: Instantiate with token
Bot-->>BotSvc: Ready
BotSvc-->>Plugin: Initialized
Plugin-->>Plugin: "Telegram server started"
end
end
sequenceDiagram
autonumber
actor User as Web Client
participant API as POST /api/ticket/id/:ticketId/message
participant Cfg as useRuntimeConfig().telegram
participant Repo as repository.telegram
participant TG as Telegram Delivery
User->>API: Send message for ticketId
API->>Cfg: Read wasabiBotId
API->>Repo: findUserByIdAndBotId(userId, wasabiBotId)
alt User found
API->>TG: Send message via Wasabi bot to telegramUser
TG-->>API: Delivery result
API-->>User: 200 OK
else Not found
API-->>User: 404/appropriate response
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate 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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
|
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/web-app/server/plugins/03.telegram.ts (1)
14-21: Blocking both bots to start any bot is a functional regressionThe early-return requires both wasabiBotId and atriumBotId. In single-bot deployments (common during phased rollout), this will skip starting even the available Wasabi bot. Also, only Wasabi is started below, so gating on Atrium being defined is unnecessary and harmful.
Start available bots individually and only skip if none are configured.
Apply this diff:
- if (!telegram.wasabiBotId || !telegram.atriumBotId) { - // No config provided - return - } - - // Start the bots (using long polling) - useCreateWasabiBot() + const hasWasabi = Boolean(telegram.wasabiBotId) + const hasAtrium = Boolean(telegram.atriumBotId) + if (!hasWasabi && !hasAtrium) { + logger.warn('Skipping Telegram: no bot IDs configured') + return + } + // Start available bots (using long polling) + if (hasWasabi) { + useCreateWasabiBot() + } + // TODO: Start Atrium bot when implemented + // if (hasAtrium) useCreateAtriumBot()apps/web-app/server/api/ticket/id/[ticketId]/message.post.ts (1)
60-65: Scope Telegram lookup/send under bot-id check and make send best-effortPrevents invalid DB queries when wasabiBotId is missing and avoids failing the entire request if Telegram send throws. The message has already been persisted; Telegram delivery should not break the API response.
Apply this diff:
- const wasabiUser = await repository.telegram.findUserByIdAndBotId(ticket.userId, telegram.wasabiBotId) - if (wasabiUser) { - // Send message to Telegram - const text = `${user.name} ${user.surname}: ${data.text}` - await useWasabiBot().api.sendMessage(wasabiUser.telegramId, text) - } + if (telegram.wasabiBotId) { + const wasabiUser = await repository.telegram.findUserByIdAndBotId(ticket.userId, telegram.wasabiBotId) + if (wasabiUser) { + // Send message to Telegram (best-effort) + const text = `${user.name} ${user.surname}: ${data.text}` + try { + await useWasabiBot().api.sendMessage(wasabiUser.telegramId, text) + } catch (err) { + // Best-effort: don't fail the request if Telegram send fails + console.error('Failed to send Telegram message via Wasabi bot', err) + } + } + }
🧹 Nitpick comments (5)
apps/web-app/.env.example (1)
25-26: Reorder keys to satisfy dotenv-linter (nitpick)The linter suggests ATRIUM should be listed before WASABI. Purely cosmetic, but it’ll keep CI green if enforced.
Apply this diff:
-NUXT_TELEGRAM_WASABI_BOT_ID= -NUXT_TELEGRAM_ATRIUM_BOT_ID= +NUXT_TELEGRAM_ATRIUM_BOT_ID= +NUXT_TELEGRAM_WASABI_BOT_ID=apps/web-app/server/api/ticket/id/[ticketId]/message.post.ts (1)
63-65: Consider Telegram message length constraints (optional)Telegram messages are limited (≈4096 chars). For longer texts, consider truncating or splitting.
If desired, I can provide a small helper to chunk and send multi-part messages.
apps/web-app/server/services/telegram/wasabi-bot.ts (2)
10-17: Make initialization idempotent to avoid double-startsIf
useCreateWasabiBotis called twice (plugin re-run, HMR, or miswiring), the second call will re-register handlers and attempt to start the bot again. Early-return ifbotis already created.export async function useCreateWasabiBot() { + if (bot) { + logger.warn('Wasabi bot already initialized; skipping re-init') + return + } const botInDb = await repository.telegram.findBot(telegram.wasabiBotId) if (!botInDb?.token) { throw new Error('Wasabi bot is not configured') } bot = new Bot(botInDb.token)
86-114: Ticket creation can race; consider a find-or-create with transactional guardTwo fast messages from the same user can pass the opened-ticket check and each create a new ticket. Prefer one of:
- Repository-level helper that atomically finds/creates an “opened” ticket (DB transaction + lock, or UPSERT with partial unique index on (user_id, status='opened')).
- DB constraint: unique index on (user_id) where status='opened', then catch conflict and re-fetch.
Optional: await the final
ctx.reply(...)to surface Telegram API errors.Also applies to: 91-102
packages/database/src/tables.ts (1)
621-629: Optionally enforce uniqueness of bot tokensA bot token should be globally unique. Add a unique index on
telegram_bots.tokento prevent accidental duplicates.-export const telegramBots = pgTable('telegram_bots', { +export const telegramBots = pgTable('telegram_bots', { id: cuid2('id').defaultRandom().primaryKey(), createdAt: timestamp('created_at', { precision: 3, withTimezone: true, mode: 'string' }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { precision: 3, withTimezone: true, mode: 'string' }).notNull().defaultNow(), firstName: varchar('first_name'), lastName: varchar('last_name'), username: varchar('username'), token: varchar('token').notNull(), -}) +}, (t) => ({ + tokenUnique: uniqueIndex('telegram_bots_token_uq').on(t.token), +}))Note: ensure
uniqueIndexis imported as shown in the previous comment.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (10)
apps/web-app/.env.example(1 hunks)apps/web-app/nuxt.config.ts(1 hunks)apps/web-app/server/api/ticket/id/[ticketId]/message.post.ts(2 hunks)apps/web-app/server/plugins/03.telegram.ts(1 hunks)apps/web-app/server/services/telegram/wasabi-bot.ts(6 hunks)packages/database/src/repository/index.ts(2 hunks)packages/database/src/repository/telegram.ts(1 hunks)packages/database/src/repository/wasabi.ts(0 hunks)packages/database/src/tables.ts(3 hunks)packages/database/src/types.ts(1 hunks)
💤 Files with no reviewable changes (1)
- packages/database/src/repository/wasabi.ts
🧰 Additional context used
🧬 Code Graph Analysis (2)
packages/database/src/repository/telegram.ts (2)
packages/database/src/tables.ts (2)
users(83-100)telegramUsers(631-650)packages/database/src/types.ts (1)
TelegramUserDraft(136-136)
packages/database/src/repository/index.ts (1)
packages/database/src/repository/telegram.ts (1)
Telegram(6-69)
🪛 dotenv-linter (3.3.0)
apps/web-app/.env.example
[warning] 26-26: [UnorderedKey] The NUXT_TELEGRAM_ATRIUM_BOT_ID key should go before the NUXT_TELEGRAM_WASABI_BOT_ID key
(UnorderedKey)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: build
- GitHub Check: Analyze (actions)
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (13)
apps/web-app/.env.example (1)
25-26: Env var names align with runtimeConfig.telegram keys — goodThese keys correctly map to runtimeConfig.telegram.wasabiBotId and runtimeConfig.telegram.atriumBotId in Nuxt. No client exposure since they’re not under NUXT_PUBLIC_.
apps/web-app/server/plugins/03.telegram.ts (1)
19-21: No action needed:useCreateWasabiBotexport verified
Confirmed inapps/web-app/server/services/telegram/wasabi-bot.ts(line 10) thatexport async function useCreateWasabiBot()is still present.apps/web-app/nuxt.config.ts (1)
22-24: No Deprecated Wasabi Token References FoundThe ripgrep search across
*.ts, *.tsx, *.js, *.mjs, *.cjs, *.vuereturned no occurrences ofwasabiToken,repository.wasabi,NUXT_TELEGRAM_WASABI_TOKEN,WasabiUser, orwasabi_users. The migration to server-only, DB-backed bot IDs inapps/web-app/nuxt.config.tsis complete—no stale references remain. Approving these changes.packages/database/src/repository/index.ts (2)
22-22: Repository facade: Telegram added — LGTMExposing
repository.telegramaligns with the new data model. Public surface looks consistent.
47-47: Telegram property wiring is correctStatic wrapper pattern is consistent with other repositories. No issues spotted.
apps/web-app/server/services/telegram/wasabi-bot.ts (3)
11-17: DB-sourced token and config guard look solidFetching the bot token from DB and failing fast when missing is the right move for multi-bot support.
52-64: Per-bot user lookup + creation aligns with the new schemaLooking up by (telegramId, botId) and creating a Telegram user bound to this bot with an access key matches the new model. Good separation from core
users.
129-142: Secure random code generation – replace getRandInteger with crypto.randomInt & verify adminId configgetRandInteger is actually defined in apps/web-app/shared/utils/random.ts and will not throw at runtime, but for cryptographic strength you should use Node’s crypto.randomInt here. Please:
• Update generateAccessCode in apps/web-app/server/services/telegram/wasabi-bot.ts to use randomInt
• Add the necessary import
• Manually confirm that telegram.adminId is set in your Nuxt config (nuxt.config.*)Apply this diff:
--- a/apps/web-app/server/services/telegram/wasabi-bot.ts +++ b/apps/web-app/server/services/telegram/wasabi-bot.ts @@ -1,5 +1,6 @@ +import { randomInt } from 'crypto' import { repository } from '../..' async function generateAccessCode(): Promise<string> { @@ -133,7 +134,7 @@ // Code should be unique while (!selectedCode) { - const code = getRandInteger(100000, 999999).toString() + const code = randomInt(100_000, 1_000_000).toString() const user = await repository.telegram.findUserByKey(code) if (!user) { selectedCode = code• No need to remove getRandInteger from shared/utils/random.ts – it’s still used elsewhere
• Verify that your nuxt.config.* has a telegram: { adminId: … } entry so the bot can locate the admin userpackages/database/src/repository/telegram.ts (2)
25-47: Queries scoped by bot and eager user fetch are correctThe
findUserByTelegramIdAndBotIdandfindUserByIdAndBotIdmethods read cleanly and return the joineduser. Good fit for the new per-bot model.
49-68: Creation/update semantics look good; updatedAt managed at the repo layerReturning the inserted/updated row and stamping
updatedAtviasql\now()`` is consistent with the rest of the codebase patterns.packages/database/src/types.ts (2)
132-137: Types surface aligns with Telegram-first schemaExported TelegramBot/TelegramUser types and drafts look correct and match tables. Clean removal of Wasabi types.
132-137: All clear: no lingering Wasabi references detectedRan a full-text search for any occurrences of
repository.wasabi,WasabiUser,wasabi_users,wasabiToken,WASABI_TOKEN, etc., and found no matches across the repo. No further action needed.packages/database/src/tables.ts (1)
1075-1088: Relations for Telegram bot/users are correct
telegramBotRelationsandtelegramUserRelationswiring looks good and consistent with FK setup.
| const { telegram } = useRuntimeConfig() | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Guard against missing wasabiBotId before DB query
If telegram.wasabiBotId is undefined, the DB predicate may evaluate with an invalid value and the route could fail at runtime. Treat Telegram delivery as best-effort: skip gracefully when the bot id isn’t configured.
I’ve proposed a concrete fix in Lines 60-65 to scope the lookup and send under a bot-id check.
🤖 Prompt for AI Agents
In apps/web-app/server/api/ticket/id/[ticketId]/message.post.ts around lines
16-17 (and the proposed change at lines 60-65), guard against
telegram.wasabiBotId being undefined before running any DB lookup or sending via
Telegram: check that telegram?.wasabiBotId is present and non-empty, and only
then include the bot-id predicate in the query and attempt the send; if it's
missing, skip the DB lookup/send path gracefully (no-op) so the route continues
without throwing.
| export const telegramUsers = pgTable('telegram_users', { | ||
| id: cuid2('id').defaultRandom().primaryKey(), | ||
| createdAt: timestamp('created_at', { precision: 3, withTimezone: true, mode: 'string' }).notNull().defaultNow(), | ||
| updatedAt: timestamp('updated_at', { precision: 3, withTimezone: true, mode: 'string' }).notNull().defaultNow(), | ||
| accessKey: varchar('access_key').notNull().unique(), | ||
| telegramId: varchar('telegram_id').notNull().unique(), | ||
| telegramId: varchar('telegram_id').notNull(), | ||
| telegramUserType: varchar('type').notNull().$type<TelegramUserType>(), | ||
| firstName: varchar('first_name'), | ||
| lastName: varchar('last_name'), | ||
| username: varchar('username'), | ||
| title: varchar('title'), | ||
| type: varchar('type').notNull().$type<TelegramUserType>(), | ||
| userId: cuid2('user_id').references(() => users.id, { | ||
| onDelete: 'cascade', | ||
| onUpdate: 'cascade', | ||
| }), | ||
| botId: cuid2('bot_id').notNull().references(() => telegramBots.id, { | ||
| onDelete: 'cascade', | ||
| onUpdate: 'cascade', | ||
| }), | ||
| }) |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Enforce uniqueness per (telegram_id, bot_id) and add supporting indexes
Without a composite unique, duplicates for the same Telegram account under the same bot are possible, leading to ambiguous lookups. Add:
- unique index on (bot_id, telegram_id)
- index on (bot_id, user_id) to speed
findUserByIdAndBotId
Apply this diff to the telegram_users table definition:
-export const telegramUsers = pgTable('telegram_users', {
+export const telegramUsers = pgTable('telegram_users', {
id: cuid2('id').defaultRandom().primaryKey(),
createdAt: timestamp('created_at', { precision: 3, withTimezone: true, mode: 'string' }).notNull().defaultNow(),
updatedAt: timestamp('updated_at', { precision: 3, withTimezone: true, mode: 'string' }).notNull().defaultNow(),
accessKey: varchar('access_key').notNull().unique(),
- telegramId: varchar('telegram_id').notNull(),
+ telegramId: varchar('telegram_id').notNull(),
telegramUserType: varchar('type').notNull().$type<TelegramUserType>(),
firstName: varchar('first_name'),
lastName: varchar('last_name'),
username: varchar('username'),
title: varchar('title'),
userId: cuid2('user_id').references(() => users.id, {
onDelete: 'cascade',
onUpdate: 'cascade',
}),
- botId: cuid2('bot_id').notNull().references(() => telegramBots.id, {
+ botId: cuid2('bot_id').notNull().references(() => telegramBots.id, {
onDelete: 'cascade',
onUpdate: 'cascade',
}),
-})
+}, (t) => ({
+ byBotAndTelegramId: uniqueIndex('telegram_users_bot_id_telegram_id_uq').on(t.botId, t.telegramId),
+ byBotAndUserId: index('telegram_users_bot_id_user_id_idx').on(t.botId, t.userId),
+}))Also add the missing imports at the top of this file:
// Add these to the existing import from 'drizzle-orm/pg-core'
import { ..., uniqueIndex, index } from 'drizzle-orm/pg-core'🤖 Prompt for AI Agents
In packages/database/src/tables.ts around lines 631 to 650, the telegram_users
table lacks a composite uniqueness constraint on (bot_id, telegram_id) and is
missing an index on (bot_id, user_id); add uniqueIndex(['bot_id',
'telegram_id']) to the table definition and add index(['bot_id', 'user_id']) to
speed lookups, and also update the import from 'drizzle-orm/pg-core' to include
uniqueIndex and index so those helpers are available.



Summary by CodeRabbit