Skip to content

feat: register user in atrium via telegram#218

Merged
hmbanan666 merged 2 commits into
mainfrom
telegram
Oct 14, 2025
Merged

feat: register user in atrium via telegram#218
hmbanan666 merged 2 commits into
mainfrom
telegram

Conversation

@hmbanan666
Copy link
Copy Markdown
Collaborator

@hmbanan666 hmbanan666 commented Oct 14, 2025

Summary by CodeRabbit

  • New Features
    • Telegram: contact-sharing authentication that issues access keys and auto-handles avatars.
    • Web app: hover to mark items as viewed and show viewer avatars.
  • Refactor
    • Centralized bot token, media download/upload, and contact-request handling across Telegram bots.
  • Style
    • Improved heading semantics, larger avatars, and smaller agreement comment text.
  • Chores
    • CI workflow updated to a newer Node setup action and cache option formatting.

@hmbanan666 hmbanan666 self-assigned this Oct 14, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Oct 14, 2025

Walkthrough

Adds shared Telegram utilities and refactors multiple bots to use them; adds Atrium bot contact handling and user creation flow; adjusts UI semantics/styles in Atrium Telegram and web-app view tracking; and bumps CI's actions/setup-node to v6 with a cache flag syntax change.

Changes

Cohort / File(s) Summary
CI workflow
\.github/workflows/ci.yml
Bump actions/setup-node from v5 → v6; change with.cache from 'pnpm' to pnpm.
Atrium Telegram UI tweaks
apps/atrium-telegram/app/components/PartnerActiveCard.vue, apps/atrium-telegram/app/components/PartnerAgreementCard.vue, apps/atrium-telegram/app/pages/agreement/[agreementId]/index.vue, apps/atrium-telegram/app/pages/navigation.vue
Semantic/styling changes: swap some divs to h3, adjust font-size classes, resize avatar containers and avatar sizes, and limit otherUsers to top 3.
Telegram common utilities
apps/core-telegram/server/services/telegram/common.ts
Add exports: getBotToken, requestContactPhone, getFileDownloadUrl, uploadToStorage; introduce S3/dir constants, logger, and enhanced error handling for media download/upload.
Telegram bots refactor
apps/core-telegram/server/services/telegram/atrium-bot.ts, apps/core-telegram/server/services/telegram/order-bot.ts, apps/core-telegram/server/services/telegram/wasabi-bot.ts
Centralize token retrieval via getBotToken; Order bot uses requestContactPhone; Wasabi bot and others call shared getFileDownloadUrl/uploadToStorage; Atrium bot adds contact handling flow (find/create user, upload avatar, create Telegram user + access key, set web app button, reply).
Web app flow item interactions
apps/web-app/app/components/FlowItemCard.vue
Add hover-triggered setAsViewed() that calls flowStore.addView if needed; render viewer avatars from item.views; refactor view-marking into function.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant AB as AtriumBot
  participant CM as CommonUtils
  participant TG as TelegramAPI
  participant DB as Database
  participant ST as Storage
  participant APP as AtriumApp

  U->>AB: share contact (message:contact)
  AB->>CM: getBotToken(atriumBotId)
  CM-->>AB: token
  AB->>TG: verify private chat
  AB->>DB: findOrCreateAtriumUser(phone)
  alt new user -> has photo
    AB->>TG: getUserProfilePhotos
    AB->>CM: getFileDownloadUrl(fileId, token, isLocalBot)
    CM-->>AB: downloadUrl
    AB->>CM: uploadToStorage(downloadUrl, fileId)
    CM-->>AB: avatarUrl
    AB->>DB: create user with avatarUrl
  end
  AB->>DB: create Telegram user + accessKey
  AB->>TG: set web app button (APP url)
  AB-->>U: reply with confirmation + accessKey
Loading
sequenceDiagram
  autonumber
  actor U as User
  participant WB as WasabiBot
  participant CM as CommonUtils
  participant TG as TelegramAPI
  participant ST as Storage
  participant DB as Database

  U->>WB: send media (photo/video/doc)
  WB->>CM: getBotToken(wasabiBotId)
  CM-->>WB: token
  WB->>CM: getFileDownloadUrl(fileId, token, isLocalBot)
  CM-->>WB: downloadUrl
  WB->>CM: uploadToStorage(downloadUrl, fileId)
  CM-->>WB: fileUrl
  WB->>DB: persist message with fileUrl
  WB-->>U: acknowledge
Loading
sequenceDiagram
  autonumber
  actor U as User
  participant FC as FlowItemCard
  participant FS as flowStore

  U->>FC: hover card
  FC->>FC: setAsViewed() (if not viewed)
  FC->>FS: addView(item.id)
  FS-->>FC: item.views updated
  FC-->>U: render viewer avatars
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

I nibble code, a floppy ear—
Tokens found, the bots draw near.
Avatars ride clouds, views hop by,
Headings stand up, fonts aim high.
CI hums v6, the build carrot's nigh. 🥕🐇

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title clearly summarizes the primary new feature of enabling user registration in Atrium via Telegram, which is the core change across multiple service files, without unnecessary detail, matching the principal intent of the pull request.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch telegram

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 and usage tips.

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: 8

Caution

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

⚠️ Outside diff range comments (1)
apps/core-telegram/server/services/telegram/wasabi-bot.ts (1)

260-263: Potential crash: tickets may be undefined

Accessing tickets.length can throw if the DB returns null/undefined. Use optional chaining.

-  if (!tickets.length || !ticket) {
+  if (!tickets?.length || !ticket) {
🧹 Nitpick comments (3)
apps/web-app/app/components/FlowItemCard.vue (1)

49-56: Optimize user lookup in the avatar rendering loop.

The userStore.users.find() call inside the v-for loop has O(n×m) complexity (n views × m users). For better performance, consider using a computed Map or memoizing the user lookups.

Apply this diff to optimize the lookup:

+const userAvatarsByViewId = computed(() => {
+  const userMap = new Map(userStore.users.map(u => [u.id, u.avatarUrl]))
+  return item?.views?.map(view => ({
+    id: view.id,
+    avatarUrl: userMap.get(view.userId)
+  })) ?? []
+})
+
 function getIconName(type: FlowItemWithData['type']): string {

Then update the template:

 <div class="flex flex-row flex-wrap gap-1.5">
   <UAvatar
-    v-for="view in item?.views"
+    v-for="view in userAvatarsByViewId"
     :key="view.id"
-    :src="userStore.users.find((user) => user.id === view.userId)?.avatarUrl ?? undefined"
+    :src="view.avatarUrl ?? undefined"
     size="md"
   />
 </div>
apps/atrium-telegram/app/components/PartnerActiveCard.vue (1)

78-78: Add hidden-user indicator when limiting avatars
Consider displaying a “+X more” badge (e.g. “+2 more”) whenever partner.value.users.filter(u ⇒ u.type !== 'partner').length exceeds 3 to surface hidden avatars.

apps/core-telegram/server/services/telegram/wasabi-bot.ts (1)

138-151: Avoid per-message DB token fetch; simplify file URL retrieval

Fetching bot token inside each handler is unnecessary and adds DB load. Since ctx.api.getFile works without the raw token, prefer removing these calls and let a shared helper construct a usable download URL.

  • Refactor common.getFileDownloadUrl to return an absolute HTTP URL when needed (using localBotApiServerUrl and token available at startup), or an absolute FS path when Bot API runs in local mode.
  • Then drop the token fetch and call getFileDownloadUrl(ctx, fileId) directly.

Example changes (paired with common.ts update in a separate comment):

-  const botToken = await getBotToken(telegram.wasabiBotId)
-  if (!botToken) {
-    return null
-  }
-  const downloadUrl = await getFileDownloadUrl({ ctx, fileId, botToken })
+  const downloadUrl = await getFileDownloadUrl({ ctx, fileId, botToken: '' /* no longer required after common.ts change */ })

Also applies to: 181-194, 223-236

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a4b33cf and 71e17a4.

📒 Files selected for processing (10)
  • .github/workflows/ci.yml (1 hunks)
  • apps/atrium-telegram/app/components/PartnerActiveCard.vue (3 hunks)
  • apps/atrium-telegram/app/components/PartnerAgreementCard.vue (1 hunks)
  • apps/atrium-telegram/app/pages/agreement/[agreementId]/index.vue (1 hunks)
  • apps/atrium-telegram/app/pages/navigation.vue (1 hunks)
  • apps/core-telegram/server/services/telegram/atrium-bot.ts (4 hunks)
  • apps/core-telegram/server/services/telegram/common.ts (2 hunks)
  • apps/core-telegram/server/services/telegram/order-bot.ts (2 hunks)
  • apps/core-telegram/server/services/telegram/wasabi-bot.ts (4 hunks)
  • apps/web-app/app/components/FlowItemCard.vue (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
apps/core-telegram/server/services/telegram/order-bot.ts (1)
apps/core-telegram/server/services/telegram/common.ts (2)
  • getBotToken (12-19)
  • requestContactPhone (40-56)
apps/core-telegram/server/services/telegram/common.ts (2)
apps/core-telegram/server/utils/logger.ts (1)
  • useLogger (3-5)
apps/web-app/server/services/db.ts (1)
  • db (164-164)
apps/core-telegram/server/services/telegram/wasabi-bot.ts (2)
apps/core-telegram/server/utils/logger.ts (1)
  • useLogger (3-5)
apps/core-telegram/server/services/telegram/common.ts (1)
  • getBotToken (12-19)
apps/core-telegram/server/services/telegram/atrium-bot.ts (2)
apps/core-telegram/server/utils/logger.ts (1)
  • useLogger (3-5)
apps/core-telegram/server/services/telegram/common.ts (5)
  • getBotToken (12-19)
  • requestContactPhone (40-56)
  • generateAccessCode (21-34)
  • getFileDownloadUrl (80-93)
  • uploadToStorage (58-78)
⏰ 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). (1)
  • GitHub Check: build
🔇 Additional comments (12)
apps/web-app/app/components/FlowItemCard.vue (2)

103-109: Good refactoring to centralize view-marking logic.

The setAsViewed() function effectively consolidates the view-tracking behavior with an appropriate guard clause to prevent redundant calls. This makes the code more maintainable and DRY.


2-2: Rethink mouseover-based view tracking for UX and mobile compatibility. Marking items viewed on mouseover can fire accidentally during scrolling and isn’t supported on touch devices; consider using an Intersection Observer with a visibility threshold (e.g., 2 s) or requiring an explicit action instead.

apps/atrium-telegram/app/pages/agreement/[agreementId]/index.vue (1)

47-47: LGTM! Consistent text sizing refinement.

The text size reduction for the agreement comment is consistent with similar changes across the Atrium Telegram UI, improving visual hierarchy.

apps/atrium-telegram/app/components/PartnerAgreementCard.vue (1)

36-36: LGTM! Consistent text sizing refinement.

The text size reduction for the agreement comment matches the change in agreement/[agreementId]/index.vue, ensuring consistent styling across agreement views.

apps/atrium-telegram/app/pages/navigation.vue (1)

12-16: LGTM! Semantic HTML improvement.

Wrapping navigation labels in h3 elements improves document structure and accessibility while maintaining the same visual styling.

apps/atrium-telegram/app/components/PartnerActiveCard.vue (2)

20-22: LGTM! Semantic HTML improvement.

Wrapping the partner name in an h3 element improves document structure and accessibility, consistent with changes in navigation.vue.


28-28: LGTM! Consistent sizing refinements.

The avatar sizing increases and relative positioning adjustment improve the visual layout of the partner card.

Also applies to: 32-32, 41-41

apps/core-telegram/server/services/telegram/order-bot.ts (1)

62-64: Good centralization of contact request

Using requestContactPhone(ctx) here is cleaner and consistent across bots.

apps/core-telegram/server/services/telegram/atrium-bot.ts (3)

14-20: Token retrieval and bot init look good

Using getBotToken and failing fast on missing token is correct.


29-33: Contact handler wiring LGTM

Registering 'message:contact' with a dedicated handler is the right approach.


61-65: Good reuse of shared contact-request helper

Centralizes UX and reduces duplication.

apps/core-telegram/server/services/telegram/common.ts (1)

40-56: Contact request helper looks good

Message, MarkdownV2, and keyboard are set appropriately.

Comment thread .github/workflows/ci.yml

- name: Node
uses: actions/setup-node@v5
uses: actions/setup-node@v6
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify the action version is the latest stable release.

The update to actions/setup-node@v6 is appropriate, but please confirm this is the latest stable version and check the release notes for any breaking changes or new features.

Also applies to: 41-41


🌐 Web query:

What is the latest stable version of actions/setup-node and are there any breaking changes in v6?

💡 Result:

Latest stable release: v5 (actions/setup-node@v5). [1][2]

v6: there is no official v6 release for actions/setup-node as of today, so no published v6 release notes; therefore no confirmed breaking changes. There are open discussions/requests (for example about removing always-auth from generated .npmrc) that might be considered for a future major — but nothing official in a v6 release yet. [1][3]

Sources:

  • actions/setup-node releases (shows v5 as latest). [1]
  • repository README / open issue about always-auth (discussion relevant to potential breaking change). [3]

Pin setup-node to the latest released version (v5). .github/workflows/ci.yml line 38 references actions/setup-node@v6, but v6 isn’t released—use actions/setup-node@v5 instead.

🤖 Prompt for AI Agents
.github/workflows/ci.yml around line 38: the workflow references
actions/setup-node@v6 which is not a released version; change the action
reference to actions/setup-node@v5 (or the exact latest released v5 tag) so the
workflow uses a valid, released setup-node version; update the line to pin to v5
and commit the updated workflow file.

Comment thread apps/core-telegram/server/services/telegram/atrium-bot.ts
Comment on lines 101 to 114
const telegramUser = await db.telegram.findClientByTelegramIdAndBotId(ctx.message.from.id.toString(), telegram.atriumBotId)
if (!telegramUser?.id) {
const accessKey = await generateAccessCode()

const createdUser = await db.telegram.createUser({
const telegramUser = await db.telegram.createUser({
telegramUserType: ctx.message.chat.type,
telegramId: ctx.message.from.id.toString(),
firstName: ctx.message.from.first_name,
lastName: ctx.message.from.last_name,
username: ctx.message.from.username,
accessKey,
botId: telegram.atriumBotId,
accessKey,
userId: user.id,
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Wrong DB lookup: use findUserByTelegramIdAndBotId (not findClient...)

This can cause duplicate records or authorization mismatches.

-  const telegramUser = await db.telegram.findClientByTelegramIdAndBotId(ctx.message.from.id.toString(), telegram.atriumBotId)
+  const telegramUser = await db.telegram.findUserByTelegramIdAndBotId(ctx.message.from.id.toString(), telegram.atriumBotId)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const telegramUser = await db.telegram.findClientByTelegramIdAndBotId(ctx.message.from.id.toString(), telegram.atriumBotId)
if (!telegramUser?.id) {
const accessKey = await generateAccessCode()
const createdUser = await db.telegram.createUser({
const telegramUser = await db.telegram.createUser({
telegramUserType: ctx.message.chat.type,
telegramId: ctx.message.from.id.toString(),
firstName: ctx.message.from.first_name,
lastName: ctx.message.from.last_name,
username: ctx.message.from.username,
accessKey,
botId: telegram.atriumBotId,
accessKey,
userId: user.id,
})
const telegramUser = await db.telegram.findUserByTelegramIdAndBotId(
ctx.message.from.id.toString(),
telegram.atriumBotId
)
if (!telegramUser?.id) {
const accessKey = await generateAccessCode()
const telegramUser = await db.telegram.createUser({
telegramUserType: ctx.message.chat.type,
telegramId: ctx.message.from.id.toString(),
firstName: ctx.message.from.first_name,
lastName: ctx.message.from.last_name,
username: ctx.message.from.username,
botId: telegram.atriumBotId,
accessKey,
userId: user.id,
})
🤖 Prompt for AI Agents
In apps/core-telegram/server/services/telegram/atrium-bot.ts around lines 101 to
114, the code calls db.telegram.findClientByTelegramIdAndBotId which is the
wrong lookup and can create duplicate or mis-authorized records; replace that
call with db.telegram.findUserByTelegramIdAndBotId, and keep the existing
telegramUser variable (do not re-declare it) so that when no user is found you
then call db.telegram.createUser(...) to create the record; ensure the lookup
uses the same telegram.atriumBotId and ctx.message.from.id.toString() parameters
and remove the inner const shadowing to correctly assign the new user to
telegramUser.

Comment on lines +105 to +116
const telegramUser = await db.telegram.createUser({
telegramUserType: ctx.message.chat.type,
telegramId: ctx.message.from.id.toString(),
firstName: ctx.message.from.first_name,
lastName: ctx.message.from.last_name,
username: ctx.message.from.username,
accessKey,
botId: telegram.atriumBotId,
accessKey,
userId: user.id,
})

logger.log('new user', createdUser?.id, ctx.message.from.id, ctx.message.text)

await ctx.reply(`Ключ доступа: ${accessKey}`)
return
}
logger.log('new user', telegramUser)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid shadowing and limit PII in logs

  • Rename the inner telegramUser variable to avoid shadowing.
  • Log only the ID to avoid leaking PII/access keys.
-    const telegramUser = await db.telegram.createUser({
+    const createdTelegramUser = await db.telegram.createUser({
       telegramUserType: ctx.message.chat.type,
       telegramId: ctx.message.from.id.toString(),
       firstName: ctx.message.from.first_name,
       lastName: ctx.message.from.last_name,
       username: ctx.message.from.username,
       botId: telegram.atriumBotId,
       accessKey,
       userId: user.id,
     })
-
-    logger.log('new user', telegramUser)
+    logger.log('new user', createdTelegramUser?.id)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const telegramUser = await db.telegram.createUser({
telegramUserType: ctx.message.chat.type,
telegramId: ctx.message.from.id.toString(),
firstName: ctx.message.from.first_name,
lastName: ctx.message.from.last_name,
username: ctx.message.from.username,
accessKey,
botId: telegram.atriumBotId,
accessKey,
userId: user.id,
})
logger.log('new user', createdUser?.id, ctx.message.from.id, ctx.message.text)
await ctx.reply(`Ключ доступа: ${accessKey}`)
return
}
logger.log('new user', telegramUser)
const createdTelegramUser = await db.telegram.createUser({
telegramUserType: ctx.message.chat.type,
telegramId: ctx.message.from.id.toString(),
firstName: ctx.message.from.first_name,
lastName: ctx.message.from.last_name,
username: ctx.message.from.username,
botId: telegram.atriumBotId,
accessKey,
userId: user.id,
})
logger.log('new user', createdTelegramUser?.id)
🤖 Prompt for AI Agents
In apps/core-telegram/server/services/telegram/atrium-bot.ts around lines 105 to
116, the created variable telegramUser shadows any outer binding and the
subsequent logger call emits PII/access keys; rename the local variable (e.g.,
createdTelegramUser or newTelegram) to avoid shadowing and change the logger to
only log the created record's ID (e.g., logger.log('new telegram user id',
createdTelegramUser.id)) so no personal info or accessKey is written to logs.

Comment on lines +154 to +171
async function getAndUploadUserPhoto(ctx: Context, botToken: string): Promise<string | null> {
if (!ctx.message?.from.id) {
return null
}

const photos = await ctx.api.getUserProfilePhotos(ctx.message.from.id)
const userPhoto = photos.photos[0]?.pop()
if (userPhoto?.file_id) {
const fileDownloadUrl = await getFileDownloadUrl({ ctx, fileId: userPhoto.file_id, botToken })
if (fileDownloadUrl) {
const uploaded = await uploadToStorage(fileDownloadUrl, userPhoto.file_id)

return uploaded?.fileUrl ?? null
}
}

return null
}
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 | 🟠 Major

Make botToken optional in helpers and handle fallback

Allow avatar upload to proceed only when a token is available; otherwise return null and use default avatar.

-async function getAndUploadUserPhoto(ctx: Context, botToken: string): Promise<string | null> {
+async function getAndUploadUserPhoto(ctx: Context, botToken?: string): Promise<string | null> {
   if (!ctx.message?.from.id) {
     return null
   }
+  if (!botToken) {
+    return null
+  }
-async function findOrCreateAtriumUser(data: { phone: string, user: { name: string, surname: string | undefined }, ctx: Context, botToken: string }): Promise<User> {
+async function findOrCreateAtriumUser(data: { phone: string, user: { name: string, surname: string | undefined }, ctx: Context, botToken?: string }): Promise<User> {
-  const user = await findOrCreateAtriumUser({
+  const user = await findOrCreateAtriumUser({
     phone: ctx.message.contact.phone_number,
     user: {
       name: ctx.message.from.first_name,
       surname: ctx.message.from.last_name,
     },
     ctx,
     botToken,
   })

Also applies to: 173-194, 91-100

🤖 Prompt for AI Agents
In apps/core-telegram/server/services/telegram/atrium-bot.ts around lines
154-171 (and similarly update occurrences at 173-194 and 91-100), change the
helpers to accept an optional botToken and update this getAndUploadUserPhoto
function to early-return null when botToken is missing: make botToken optional
in the signature (botToken?: string), check if (!botToken) return null before
calling Telegram API or getFileDownloadUrl, and only call
getFileDownloadUrl/uploadToStorage when botToken is present; also update any
helper typings and call sites in the other referenced ranges to pass through or
guard the optional token and fall back to using the default avatar when null is
returned.

Comment on lines +58 to +78
export async function uploadToStorage(downloadUrl: string, fileId: string) {
try {
const extension = downloadUrl.split('.').pop()

const buffer = await fs.readFile(downloadUrl)
if (!buffer) {
return null
}

const fileInnerUri = `/${S3_TELEGRAM_DIRECTORY}/${fileId}.${extension}`
const fileUrl = `${mediaUrl}${fileInnerUri}`

const storage = useStorage('s3')
await storage.setItemRaw(fileInnerUri, buffer)

return { fileUrl }
} catch (e) {
logger.error('uploadToStorage', e)
return null
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

uploadToStorage should handle both local FS paths and HTTP URLs

fs.readFile on a URL will fail; file_path from Telegram may be relative. Support:

  • Absolute FS path: read with fs.
  • HTTP(S) URL: fetch and convert to Buffer.
  • Robust extension extraction.
-export async function uploadToStorage(downloadUrl: string, fileId: string) {
+export async function uploadToStorage(downloadUrl: string, fileId: string) {
   try {
-    const extension = downloadUrl.split('.').pop()
-
-    const buffer = await fs.readFile(downloadUrl)
+    let buffer: Buffer
+    let extension = 'bin'
+    if (downloadUrl.startsWith('http://') || downloadUrl.startsWith('https://')) {
+      const res = await fetch(downloadUrl)
+      if (!res.ok) {
+        throw new Error(`Failed to fetch file: ${res.status} ${res.statusText}`)
+      }
+      const ab = await res.arrayBuffer()
+      buffer = Buffer.from(ab)
+      const pathname = new URL(downloadUrl).pathname
+      extension = pathname.split('.').pop() || 'bin'
+    } else {
+      buffer = await fs.readFile(downloadUrl)
+      const parts = downloadUrl.split('?')[0].split('#')[0]
+      extension = parts.split('.').pop() || 'bin'
+    }
     if (!buffer) {
       return null
     }

Add missing import (outside this hunk):

// at top-level imports
// uses global fetch in Node 18+; if not available in your runtime, import 'undici' or node-fetch accordingly.
🤖 Prompt for AI Agents
In apps/core-telegram/server/services/telegram/common.ts around lines 58 to 78,
update uploadToStorage to support three downloadUrl forms: if downloadUrl starts
with 'http://' or 'https://' use fetch() and Buffer.from(await
response.arrayBuffer()); else if path.isAbsolute(downloadUrl) use
fs.readFile(downloadUrl); else treat as relative path and resolve with
path.resolve(process.cwd(), downloadUrl) (or the Telegram file base dir if
available) then fs.readFile; extract extension robustly using
path.extname(downloadUrl) || new URL(downloadUrl,
'http://example.com').pathname.split('.').pop() || 'bin'; add required imports
at top: import fs from 'fs/promises'; import path from 'path'; and note to
include global fetch support (or import undici/node-fetch) if runtime lacks it;
preserve existing S3 upload logic and error handling.

Comment on lines +80 to +93
export async function getFileDownloadUrl(data: { ctx: Context, fileId: string, botToken: string }): Promise<string | null> {
try {
const file = await data.ctx.api.getFile(data.fileId)
if (!file) {
return null
}

// /var/lib/bot/token/documents/file_id.ext
return file.file_path ?? null
} catch (e) {
logger.error('getFileDownloadUrl', e)
return null
}
}
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 | 🟠 Major

Return a usable download source (absolute FS path or full HTTP URL)

Construct the full URL when file_path isn’t absolute. Use localBotApiServerUrl and the token.

-export async function getFileDownloadUrl(data: { ctx: Context, fileId: string, botToken: string }): Promise<string | null> {
+export async function getFileDownloadUrl(data: { ctx: Context, fileId: string, botToken: string }): Promise<string | null> {
   try {
     const file = await data.ctx.api.getFile(data.fileId)
     if (!file) {
       return null
     }
 
-    // /var/lib/bot/token/documents/file_id.ext
-    return file.file_path ?? null
+    const fp = file.file_path ?? null
+    if (!fp) return null
+    // If Bot API runs in local mode and returns an absolute path, use it directly.
+    if (fp.startsWith('/')) {
+      return fp
+    }
+    // Otherwise build an absolute HTTP URL to the Bot API server.
+    return `${telegram.localBotApiServerUrl}/file/bot${data.botToken}/${fp}`
   } catch (e) {
     logger.error('getFileDownloadUrl', e)
     return null
   }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function getFileDownloadUrl(data: { ctx: Context, fileId: string, botToken: string }): Promise<string | null> {
try {
const file = await data.ctx.api.getFile(data.fileId)
if (!file) {
return null
}
// /var/lib/bot/token/documents/file_id.ext
return file.file_path ?? null
} catch (e) {
logger.error('getFileDownloadUrl', e)
return null
}
}
export async function getFileDownloadUrl(data: { ctx: Context, fileId: string, botToken: string }): Promise<string | null> {
try {
const file = await data.ctx.api.getFile(data.fileId)
if (!file) {
return null
}
const fp = file.file_path ?? null
if (!fp) return null
// If Bot API runs in local mode and returns an absolute FS path, use it directly.
if (fp.startsWith('/')) {
return fp
}
// Otherwise build an absolute HTTP URL to the Bot API server.
return `${telegram.localBotApiServerUrl}/file/bot${data.botToken}/${fp}`
} catch (e) {
logger.error('getFileDownloadUrl', e)
return null
}
}
🤖 Prompt for AI Agents
In apps/core-telegram/server/services/telegram/common.ts around lines 80 to 93,
getFileDownloadUrl currently returns file.file_path raw; update it so when
file.file_path is an absolute filesystem path (starts with '/') return it as-is,
otherwise construct and return the full HTTP download URL using the
localBotApiServerUrl and botToken (e.g.
`${localBotApiServerUrl}/file/bot${botToken}/${file.file_path}`), preserving
null behavior and existing try/catch logging; ensure you import or access
localBotApiServerUrl from config and URL-encode the file_path component.

Comment thread apps/core-telegram/server/services/telegram/order-bot.ts
@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
5.1% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

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: 0

Caution

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

⚠️ Outside diff range comments (2)
apps/core-telegram/server/services/telegram/wasabi-bot.ts (1)

260-270: Fix potential crash when no tickets are returned

tickets can be undefined; accessing tickets.length would throw.

-  const tickets = await db.ticket.listOpenedByUser(telegramUser.user.id)
-  let ticket = tickets?.[0]
-  if (!tickets.length || !ticket) {
+  const tickets = (await db.ticket.listOpenedByUser(telegramUser.user.id)) ?? []
+  let ticket = tickets[0]
+  if (!tickets.length) {
     // Create ticket
     ticket = await db.ticket.create({
       title: `${telegramUser.user.name} ${telegramUser.user.surname}`,
       description: 'Создано автоматически',
       userId: telegramUser.user.id,
       status: 'opened',
     })
   }
apps/core-telegram/server/services/telegram/common.ts (1)

21-34: Add missing import for getRandInteger
generateAccessCode references getRandInteger but it’s not imported; add at the top of common.ts:

import { getRandInteger } from '../../../shared/utils/random'
♻️ Duplicate comments (3)
apps/core-telegram/server/services/telegram/atrium-bot.ts (2)

105-116: Limit PII in logs; avoid logging whole record

Log only the created record ID.

-    const createdTelegramUser = await db.telegram.createUser({
+    const createdTelegramUser = await db.telegram.createUser({
       telegramUserType: ctx.message.chat.type,
       telegramId: ctx.message.from.id.toString(),
       firstName: ctx.message.from.first_name,
       lastName: ctx.message.from.last_name,
       username: ctx.message.from.username,
       botId: telegram.atriumBotId,
       accessKey,
       userId: user.id,
     })
-
-    logger.log('new user', createdTelegramUser)
+    logger.log('new user', createdTelegramUser?.id)

86-90: Do not block registration if Wasabi token is missing; make botToken optional

Proceed with default avatar when token is absent. Update helper signatures and early-return in photo fetch only.

-  const botToken = await getBotToken(telegram.wasabiBotId)
-  if (!botToken) {
-    return null
-  }
+  const botToken = (await getBotToken(telegram.wasabiBotId)) ?? undefined
-async function getAndUploadUserPhoto(ctx: Context, botToken: string): Promise<string | null> {
+async function getAndUploadUserPhoto(ctx: Context, botToken?: string): Promise<string | null> {
   if (!ctx.message?.from.id) {
     return null
   }
+  if (!botToken) {
+    return null
+  }
-async function findOrCreateAtriumUser(data: { phone: string, user: { name: string, surname: string | undefined }, ctx: Context, botToken: string }): Promise<User> {
+async function findOrCreateAtriumUser(data: { phone: string, user: { name: string, surname: string | undefined }, ctx: Context, botToken?: string }): Promise<User> {

Also applies to: 154-171, 173-194

apps/core-telegram/server/services/telegram/common.ts (1)

58-88: Fix buffer type for HTTP downloads and improve extension parsing

Ensure storage receives a Buffer; handle querystrings/fragment safely; keep behavior for local FS and HTTP.

-export async function uploadToStorage(downloadUrl: string, fileId: string) {
+export async function uploadToStorage(downloadUrl: string, fileId: string) {
   try {
-    const extension = downloadUrl.split('.').pop()
-
-    let buffer
-
-    if (downloadUrl.startsWith('http://') || downloadUrl.startsWith('https://')) {
-      // Download file
-      const response = await fetch(downloadUrl)
-      buffer = await response.arrayBuffer()
-    } else {
-      // Read file
-      buffer = await fs.readFile(downloadUrl)
-    }
+    // Derive extension robustly
+    let extension = 'bin'
+    let buffer: Buffer
+    if (downloadUrl.startsWith('http://') || downloadUrl.startsWith('https://')) {
+      const response = await fetch(downloadUrl)
+      if (!response.ok) {
+        throw new Error(`Failed to fetch file: ${response.status} ${response.statusText}`)
+      }
+      const ab = await response.arrayBuffer()
+      buffer = Buffer.from(ab)
+      const pathname = new URL(downloadUrl).pathname
+      const ext = pathname.split('.').pop()
+      extension = ext || 'bin'
+    } else {
+      // Strip query/hash if any (defensive)
+      const sanitized = downloadUrl.split('?')[0].split('#')[0]
+      buffer = await fs.readFile(sanitized)
+      const ext = sanitized.split('.').pop()
+      extension = ext || 'bin'
+    }
 
     if (!buffer) {
       return null
     }
 
     const fileInnerUri = `/${S3_TELEGRAM_DIRECTORY}/${fileId}.${extension}`
     const fileUrl = `${mediaUrl}${fileInnerUri}`
 
     const storage = useStorage('s3')
     await storage.setItemRaw(fileInnerUri, buffer)
 
     return { fileUrl }
   } catch (e) {
     logger.error('uploadToStorage', e)
     return null
   }
 }
🧹 Nitpick comments (6)
apps/core-telegram/server/services/telegram/order-bot.ts (2)

19-19: Verify API root consistency with other bots

Wasabi bot uses client.apiRoot (local Bot API). If this bot also runs against the local API, pass client: { apiRoot: telegram.localBotApiServerUrl } for consistency.


92-106: Avoid variable shadowing and limit PII in logs

Rename inner variable and log only the ID to avoid leaking personal data/access keys.

-  if (!telegramUser?.id) {
-    const telegramUser = await db.telegram.createUser({
+  if (!telegramUser?.id) {
+    const createdTelegramUser = await db.telegram.createUser({
       telegramUserType: ctx.message.chat.type,
       telegramId: ctx.message.from.id.toString(),
       firstName: ctx.message.from.first_name,
       lastName: ctx.message.from.last_name,
       username: ctx.message.from.username,
       botId: telegram.orderBotId,
       accessKey: createId(),
       clientId: client.id,
     })
-
-    logger.log('new user', telegramUser)
+    logger.log('new user', createdTelegramUser?.id)
apps/core-telegram/server/services/telegram/wasabi-bot.ts (2)

138-151: Remove redundant botToken fetch when using local file paths

With isLocalBot: true, getFileDownloadUrl returns a filesystem path and does not need botToken. Drop the extra DB call to reduce latency and load. Adjust common.getFileDownloadUrl to accept botToken as optional (see comment in common.ts).

-  const botToken = await getBotToken(telegram.wasabiBotId)
-  if (!botToken) {
-    return null
-  }
   let fileUrl
-  const downloadUrl = await getFileDownloadUrl({ ctx, fileId, botToken, isLocalBot: true })
+  const downloadUrl = await getFileDownloadUrl({ ctx, fileId, isLocalBot: true })

Also applies to: 181-195, 223-236


143-151: Initialize fileUrl to null to avoid undefined

Ensure a consistent nullable type.

-  let fileUrl
+  let fileUrl: string | null = null

Also applies to: 186-194, 228-236

apps/core-telegram/server/services/telegram/atrium-bot.ts (1)

118-127: Avoid hardcoded web_app URL

Consider moving 'https://t.me/sushi_atrium_bot/app' to runtime config to ease environment changes.

apps/core-telegram/server/services/telegram/common.ts (1)

90-108: Allow skipping botToken when using local file paths

Make botToken optional; only required for remote Telegram file URL generation. This enables wasabi-bot to drop redundant token lookups.

-export async function getFileDownloadUrl(data: { ctx: Context, fileId: string, botToken: string, isLocalBot: boolean }): Promise<string | null> {
+export async function getFileDownloadUrl(data: { ctx: Context, fileId: string, botToken?: string, isLocalBot: boolean }): Promise<string | null> {
   try {
     const file = await data.ctx.api.getFile(data.fileId)
     if (!file?.file_path) {
       return null
     }
 
     // as /var/lib/bot/token/documents/file_id.ext
     if (data.isLocalBot) {
       return file.file_path
     }
 
     // or from telegram api
-    return `https://api.telegram.org/file/bot${data.botToken}/${file.file_path}`
+    if (!data.botToken) {
+      return null
+    }
+    return `https://api.telegram.org/file/bot${data.botToken}/${file.file_path}`
   } catch (e) {
     logger.error('getFileDownloadUrl', e)
     return null
   }
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 71e17a4 and 8d1a60b.

📒 Files selected for processing (4)
  • apps/core-telegram/server/services/telegram/atrium-bot.ts (4 hunks)
  • apps/core-telegram/server/services/telegram/common.ts (2 hunks)
  • apps/core-telegram/server/services/telegram/order-bot.ts (2 hunks)
  • apps/core-telegram/server/services/telegram/wasabi-bot.ts (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
apps/core-telegram/server/services/telegram/order-bot.ts (1)
apps/core-telegram/server/services/telegram/common.ts (2)
  • getBotToken (12-19)
  • requestContactPhone (40-56)
apps/core-telegram/server/services/telegram/common.ts (1)
apps/core-telegram/server/utils/logger.ts (1)
  • useLogger (3-5)
apps/core-telegram/server/services/telegram/atrium-bot.ts (2)
apps/core-telegram/server/utils/logger.ts (1)
  • useLogger (3-5)
apps/core-telegram/server/services/telegram/common.ts (5)
  • getBotToken (12-19)
  • requestContactPhone (40-56)
  • generateAccessCode (21-34)
  • getFileDownloadUrl (90-108)
  • uploadToStorage (58-88)
apps/core-telegram/server/services/telegram/wasabi-bot.ts (2)
apps/core-telegram/server/utils/logger.ts (1)
  • useLogger (3-5)
apps/core-telegram/server/services/telegram/common.ts (2)
  • getBotToken (12-19)
  • getFileDownloadUrl (90-108)
⏰ 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). (1)
  • GitHub Check: build
🔇 Additional comments (8)
apps/core-telegram/server/services/telegram/order-bot.ts (2)

14-20: LGTM: centralized token retrieval and clear error

Token fetch via getBotToken and the "Order bot is not configured" message are correct.


62-64: Good reuse of shared contact flow

Using requestContactPhone centralizes UX and formatting.

apps/core-telegram/server/services/telegram/wasabi-bot.ts (1)

13-21: LGTM: startup with local Bot API

Token retrieval and configuring client.apiRoot are correct.

apps/core-telegram/server/services/telegram/atrium-bot.ts (3)

14-20: LGTM: centralized token retrieval and startup

Initialization via getBotToken + clear error is good.


29-33: LGTM: contact handler wiring

Contact message route added cleanly.


62-65: Good reuse of shared contact flow

requestContactPhone centralizes UX.

apps/core-telegram/server/services/telegram/common.ts (2)

12-19: LGTM: token retrieval helper

Centralizing token lookup is good.


40-56: LGTM: shared contact prompt

Consistent UX with MarkdownV2 and keyboard.

@hmbanan666 hmbanan666 merged commit 0ee3b03 into main Oct 14, 2025
7 of 8 checks passed
@hmbanan666 hmbanan666 deleted the telegram branch October 14, 2025 09:17
@coderabbitai coderabbitai Bot mentioned this pull request Oct 14, 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