Skip to content

feat: upload photo to s3#159

Merged
hmbanan666 merged 1 commit intomainfrom
photo-to-s3
Sep 16, 2025
Merged

feat: upload photo to s3#159
hmbanan666 merged 1 commit intomainfrom
photo-to-s3

Conversation

@hmbanan666
Copy link
Copy Markdown
Collaborator

@hmbanan666 hmbanan666 commented Sep 16, 2025

Summary by CodeRabbit

  • New Features
    • Telegram media (photos, videos, documents) sent to the bot now appear in tickets with direct, public file links.
    • Highest-quality photo is selected automatically; captions are preserved as message text.
    • More reliable media handling with consistent file availability and improved logging for support.
  • Chores
    • Expanded data model to store optional Telegram file IDs and public file URLs for each ticket message.

@hmbanan666 hmbanan666 self-assigned this Sep 16, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Sep 16, 2025

Walkthrough

Added S3-backed persistence for Telegram media in the Wasabi bot, generating public file URLs from runtime config. Media handlers (photo/video/document) now download via Telegram API using bot token, store bytes in S3, and persist telegramFileId/fileUrl/text. Database schema updated to add telegram_file_id and file_url columns.

Changes

Cohort / File(s) Summary
Telegram bot media handling
apps/web-app/server/services/telegram/wasabi-bot.ts
Implemented media download via Telegram API, S3 storage under /telegram/files/{fileId}.{ext}, and public URL construction from runtime mediaUrl. Updated photo/video/document handlers to persist telegramFileId, fileUrl, and caption-based text. Enhanced logging and early-exit checks.
Database schema updates
packages/database/src/tables.ts
Extended ticket_messages with optional telegram_file_id and file_url varchar columns; exported table shape updated accordingly.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as Telegram User
  participant TG as Telegram
  participant WB as Wasabi Bot (server)
  participant TAPI as Telegram API
  participant S3 as S3 Storage
  participant DB as Database

  U->>TG: Send photo/video/document (+optional caption)
  TG->>WB: Webhook update (message with file_id)
  WB->>TAPI: getFile(file_id) using bot token
  alt downloadUrl available
    TAPI-->>WB: file download URL
    WB->>TAPI: HTTP GET file bytes
    TAPI-->>WB: Binary media
    WB->>WB: Derive extension, build fileUri
    WB->>S3: PUT /telegram/files/{fileId}.{ext} (bytes)
    S3-->>WB: 200 OK
    WB->>DB: Insert ticket_message { text(caption), telegramFileId, fileUrl }
    DB-->>WB: Inserted
  else missing URL
    WB->>DB: Insert ticket_message { text(caption or empty) }
    DB-->>WB: Inserted
  end
  WB-->>TG: Acknowledge handling
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

I hop through chats where pixels flow,
Fetching bytes from snowy Telegram snow.
Into S3 they softly drop,
A shiny URL—carrot on top! 🥕
Tickets now remember each file’s glow,
Thump-thump logs, and off I go.

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 "feat: upload photo to s3" is concise and directly related to a primary change in the PR—adding S3 persistence for Telegram media and exposing a public file URL—so it is not misleading or vague; however, the changeset also includes support for other media types and DB schema additions that the title does not mention.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch photo-to-s3

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


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.

@sonarqubecloud
Copy link
Copy Markdown

@hmbanan666 hmbanan666 merged commit d3e69f5 into main Sep 16, 2025
7 of 8 checks passed
@hmbanan666 hmbanan666 deleted the photo-to-s3 branch September 16, 2025 12:52
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: 1

🧹 Nitpick comments (7)
packages/database/src/tables.ts (1)

706-707: Consider text() for fileUrl for very long CDN URLs.

If URLs can exceed varchar limits in some drivers, prefer text('file_url') for portability. Optional.

-  telegramFileId: varchar('telegram_file_id'),
-  fileUrl: varchar('file_url'),
+  telegramFileId: varchar('telegram_file_id'),
+  fileUrl: text('file_url'),
apps/web-app/server/services/telegram/wasabi-bot.ts (6)

143-156: Harden photo upload: robust extension, URL join, and no leading slash in key.

  • Derive extension from URL pathname (no query), default to 'jpg' if missing.
  • Use URL() to join mediaUrl + key to avoid double slashes.
  • Avoid leading slash in S3 key (driver/bucket portability).
-  const downloadUrl = await getFileDownloadUrl({ ctx, fileId, botToken })
+  const downloadUrl = await getFileDownloadUrl({ ctx, fileId, botToken })

   if (!downloadUrl) {
     return
   }

-  const extension = downloadUrl.split('.').pop()
-  const buffer = await fetch(downloadUrl).then((res) => res.arrayBuffer())
+  const { pathname } = new URL(downloadUrl)
+  const dot = pathname.lastIndexOf('.')
+  const extension = dot > -1 ? pathname.slice(dot + 1) : 'jpg'
+  const buffer = await fetch(downloadUrl).then((res) => res.arrayBuffer())

-  const fileUri = `/${S3_TELEGRAM_DIRECTORY}/${fileId}.${extension}`
-  const fileUrl = `${mediaUrl}${fileUri}`
+  const key = `${S3_TELEGRAM_DIRECTORY}/${fileId}.${extension}`
+  const fileUrl = new URL(key, mediaUrl).toString()
 
-  const storage = useStorage('s3')
-  await storage.setItemRaw(fileUri, buffer)
+  const storage = useStorage('s3')
+  await storage.setItemRaw(key, buffer)

138-146: Return type nit: use return instead of return null.

These handlers are effectively Promise. Returning null widens the type.

-  if (!botToken) {
-    return null
-  }
+  if (!botToken) {
+    return
+  }

Apply the same pattern in video and file handlers.


179-185: Video path: downloadUrl is computed but unused; either upload to S3 or drop the call.

Right now you fetch the URL only to log it (and we’re removing that log). Avoid extra Telegram API calls or complete the S3 flow for parity.

-  const downloadUrl = await getFileDownloadUrl({ ctx, fileId: ctx.message.video.file_id, botToken })
+  // TODO: If you want S3 parity with photos, fetch and store like in handlePhoto.
+  // Otherwise, remove this call to avoid an extra API hit.

Also applies to: 189-194


207-213: Document path: same as video — unused downloadUrl and no S3 upload.

Mirror photo handling to persist fileUrl or remove the call.

-  const downloadUrl = await getFileDownloadUrl({ ctx, fileId: ctx.message.document.file_id, botToken })
+  // TODO: If needed, compute and upload to S3 like in handlePhoto.

Also applies to: 217-223


7-8: Config join robustness and naming nit.

  • Consider deriving mediaBaseUrl once (without trailing slash) to prevent accidental // joins elsewhere.
-const S3_TELEGRAM_DIRECTORY = 'telegram/files'
-const { telegram, public: { mediaUrl } } = useRuntimeConfig()
+const S3_TELEGRAM_DIRECTORY = 'telegram/files'
+const { telegram, public: { mediaUrl } } = useRuntimeConfig()
+const mediaBaseUrl = mediaUrl.endsWith('/') ? mediaUrl.slice(0, -1) : mediaUrl

And then use new URL(key, mediaBaseUrl).toString() (see photo diff).

Also applies to: 10-10


151-156: Optional: set Content-Type via transaction headers when calling setItemRaw

Unstorage's S3/HTTP driver maps transaction option headers to the underlying request — pass Content-Type as an options header: await storage.setItemRaw(fileUri, buffer, { headers: { 'Content-Type': mimeType } }). Derive mimeType from extension (e.g., 'image/jpeg' for jpg).

File: apps/web-app/server/services/telegram/wasabi-bot.ts Lines: 151–156

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b8a07a2 and d551211.

📒 Files selected for processing (2)
  • apps/web-app/server/services/telegram/wasabi-bot.ts (4 hunks)
  • packages/database/src/tables.ts (1 hunks)
⏰ 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 (2)
packages/database/src/tables.ts (1)

706-707: LGTM on new columns; confirm migrations and repository types are updated.

telegramFileId and fileUrl additions look consistent with usage in wasabi-bot. Please ensure a Drizzle migration is generated/applied and repository.ticket.createMessage accepts these fields in its insert type.

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

160-163: Repository type alignment for new fields.

Ensure repository.ticket.createMessage accepts telegramFileId and fileUrl; otherwise TS will complain or fields will be dropped.


// Save photo?
logger.log('photo', data.user.id, ctx.message.from.id, ctx.message.text, ctx.message.photo, downloadUrl)
logger.log('photo', data.user.id, ctx.message.from.id, ctx.message.caption, ctx.message.photo, downloadUrl)
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

Stop logging downloadUrl — it embeds the Telegram bot token (secret leak).

downloadUrl contains bot<token> and will leak credentials into logs.

Apply this diff to remove the secret from logs:

-  logger.log('photo', data.user.id, ctx.message.from.id, ctx.message.caption, ctx.message.photo, downloadUrl)
+  logger.log('photo', data.user.id, ctx.message.from.id, ctx.message.caption, ctx.message.photo)
-  logger.log('video', data.user.id, ctx.message.from.id, ctx.message.text, ctx.message.caption, ctx.message.video, downloadUrl)
+  logger.log('video', data.user.id, ctx.message.from.id, ctx.message.text, ctx.message.caption, ctx.message.video)
-  logger.log('file', data.user.id, ctx.message.from.id, ctx.message.text, ctx.message.caption, ctx.message.document, downloadUrl)
+  logger.log('file', data.user.id, ctx.message.from.id, ctx.message.text, ctx.message.caption, ctx.message.document)

Also applies to: 193-193, 221-221

🤖 Prompt for AI Agents
In apps/web-app/server/services/telegram/wasabi-bot.ts around lines 165, 193 and
221, the logger.log calls include downloadUrl which contains the bot token and
leaks secrets; remove downloadUrl from the arguments passed to logger.log (log
the other fields like 'photo', user ids, caption and photo metadata only) or
replace it with a non-sensitive placeholder (e.g., '[REDACTED]' or true/false)
before logging to avoid embedding the bot token in logs.

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