Skip to content

chore: telegram bot rework#76

Merged
hmbanan666 merged 1 commit intomainfrom
telegram-rework
Aug 19, 2025
Merged

chore: telegram bot rework#76
hmbanan666 merged 1 commit intomainfrom
telegram-rework

Conversation

@hmbanan666
Copy link
Copy Markdown
Collaborator

@hmbanan666 hmbanan666 commented Aug 19, 2025

Summary by CodeRabbit

  • New Features
    • Added support for multiple Telegram bots (Wasabi and Atrium).
    • Users are now linked to a specific bot, ensuring messages and tickets route correctly.
  • Improvements
    • More reliable Telegram bot initialization with clearer error handling.
    • Enhanced access key flow and per-bot user management for smoother onboarding.
  • Chores
    • Updated environment and runtime configuration to use bot IDs instead of a single token.
    • Backend data model aligned with Telegram bots and users to support multi-bot operations.

@hmbanan666 hmbanan666 self-assigned this Aug 19, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Aug 19, 2025

Walkthrough

Replaces 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

Cohort / File(s) Summary
Env & Runtime Config
apps/web-app/.env.example, apps/web-app/nuxt.config.ts
Replace wasabiToken with wasabiBotId and atriumBotId in env and runtimeConfig.telegram; adminId unchanged.
Server Plugin (Telegram init)
apps/web-app/server/plugins/03.telegram.ts
Plugin now requires both wasabiBotId and atriumBotId; early-return if missing; initialization path unchanged.
Ticket Message API
apps/web-app/server/api/ticket/id/[ticketId]/message.post.ts
Uses repository.telegram.findUserByIdAndBotId(userId, telegram.wasabiBotId); pulls telegram config via useRuntimeConfig(); replaces repository.wasabi usage.
Telegram Bot Service
apps/web-app/server/services/telegram/wasabi-bot.ts
Loads bot token from DB via repository.telegram.findBot(wasabiBotId); user lookups/creation and access keyed by botId; introduces useWasabiBot() export; replaces Wasabi repo calls with Telegram repo.
Repository Facade
packages/database/src/repository/index.ts
Adds telegram property; removes wasabi property; updates facade imports/exports accordingly.
Repository Implementations
packages/database/src/repository/telegram.ts, packages/database/src/repository/wasabi.ts
Adds Telegram repository with bot/user CRUD and lookups (bot- and key-scoped); removes Wasabi repository entirely.
Database Schema & Relations
packages/database/src/tables.ts
Removes wasabi_users; adds telegram_bots and telegram_users with botId FK; updates relations to use Telegram entities; telegramId no longer unique.
Types
packages/database/src/types.ts
Removes WasabiUser types; adds TelegramBot/TelegramUser types; adds TicketMessage and ActivitySchedule types.

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
Loading
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

A rabbit taps keys with a thump-thump cheer,
Bots now in pairs, their IDs crystal clear.
Tokens retire, to the database they hop,
New tables sprout and relations don’t stop.
Messages route by bot and by id—
Carrots for code that’s neatly applied! 🥕✨

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 Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch telegram-rework

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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 regression

The 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-effort

Prevents 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-starts

If useCreateWasabiBot is called twice (plugin re-run, HMR, or miswiring), the second call will re-register handlers and attempt to start the bot again. Early-return if bot is 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 guard

Two 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 tokens

A bot token should be globally unique. Add a unique index on telegram_bots.token to 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 uniqueIndex is 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 86390ae and ac31ead.

📒 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 — good

These 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: useCreateWasabiBot export verified
Confirmed in apps/web-app/server/services/telegram/wasabi-bot.ts (line 10) that export async function useCreateWasabiBot() is still present.

apps/web-app/nuxt.config.ts (1)

22-24: No Deprecated Wasabi Token References Found

The ripgrep search across *.ts, *.tsx, *.js, *.mjs, *.cjs, *.vue returned no occurrences of wasabiToken, repository.wasabi, NUXT_TELEGRAM_WASABI_TOKEN, WasabiUser, or wasabi_users. The migration to server-only, DB-backed bot IDs in apps/web-app/nuxt.config.ts is complete—no stale references remain. Approving these changes.

packages/database/src/repository/index.ts (2)

22-22: Repository facade: Telegram added — LGTM

Exposing repository.telegram aligns with the new data model. Public surface looks consistent.


47-47: Telegram property wiring is correct

Static 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 solid

Fetching 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 schema

Looking 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 config

getRandInteger 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 user

packages/database/src/repository/telegram.ts (2)

25-47: Queries scoped by bot and eager user fetch are correct

The findUserByTelegramIdAndBotId and findUserByIdAndBotId methods read cleanly and return the joined user. Good fit for the new per-bot model.


49-68: Creation/update semantics look good; updatedAt managed at the repo layer

Returning the inserted/updated row and stamping updatedAt via sql\now()`` is consistent with the rest of the codebase patterns.

packages/database/src/types.ts (2)

132-137: Types surface aligns with Telegram-first schema

Exported TelegramBot/TelegramUser types and drafts look correct and match tables. Clean removal of Wasabi types.


132-137: All clear: no lingering Wasabi references detected

Ran 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

telegramBotRelations and telegramUserRelations wiring looks good and consistent with FK setup.

Comment on lines +16 to +17
const { telegram } = useRuntimeConfig()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ 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.

Comment on lines +631 to 650
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',
}),
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ 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.

@hmbanan666 hmbanan666 merged commit a9c82fc into main Aug 19, 2025
8 checks passed
@hmbanan666 hmbanan666 deleted the telegram-rework branch August 19, 2025 08:55
This was referenced Sep 1, 2025
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.

1 participant