feat: register user in atrium via telegram#218
Conversation
WalkthroughAdds 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
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
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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 undefinedAccessing 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 thev-forloop 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”) wheneverpartner.value.users.filter(u ⇒ u.type !== 'partner').lengthexceeds 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 retrievalFetching 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
📒 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 onmouseovercan 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
h3elements 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
h3element improves document structure and accessibility, consistent with changes innavigation.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 requestUsing 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 goodUsing getBotToken and failing fast on missing token is correct.
29-33: Contact handler wiring LGTMRegistering 'message:contact' with a dedicated handler is the right approach.
61-65: Good reuse of shared contact-request helperCentralizes UX and reduces duplication.
apps/core-telegram/server/services/telegram/common.ts (1)
40-56: Contact request helper looks goodMessage, MarkdownV2, and keyboard are set appropriately.
|
|
||
| - name: Node | ||
| uses: actions/setup-node@v5 | ||
| uses: actions/setup-node@v6 |
There was a problem hiding this comment.
🧩 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.
| 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, | ||
| }) |
There was a problem hiding this comment.
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.
| 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.
| 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) |
There was a problem hiding this comment.
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.
| 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.
| 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 | ||
| } |
There was a problem hiding this comment.
🛠️ 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.
| 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 | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
| 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 | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ 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.
| 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.
|
There was a problem hiding this comment.
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 returnedtickets 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 recordLog 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 optionalProceed 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 parsingEnsure 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 botsWasabi 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 logsRename 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 pathsWith 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 undefinedEnsure a consistent nullable type.
- let fileUrl + let fileUrl: string | null = nullAlso applies to: 186-194, 228-236
apps/core-telegram/server/services/telegram/atrium-bot.ts (1)
118-127: Avoid hardcoded web_app URLConsider 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 pathsMake 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
📒 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 errorToken fetch via getBotToken and the "Order bot is not configured" message are correct.
62-64: Good reuse of shared contact flowUsing requestContactPhone centralizes UX and formatting.
apps/core-telegram/server/services/telegram/wasabi-bot.ts (1)
13-21: LGTM: startup with local Bot APIToken retrieval and configuring client.apiRoot are correct.
apps/core-telegram/server/services/telegram/atrium-bot.ts (3)
14-20: LGTM: centralized token retrieval and startupInitialization via getBotToken + clear error is good.
29-33: LGTM: contact handler wiringContact message route added cleanly.
62-65: Good reuse of shared contact flowrequestContactPhone centralizes UX.
apps/core-telegram/server/services/telegram/common.ts (2)
12-19: LGTM: token retrieval helperCentralizing token lookup is good.
40-56: LGTM: shared contact promptConsistent UX with MarkdownV2 and keyboard.


Summary by CodeRabbit