docs: add telegram uploader backend design spec
This commit is contained in:
@@ -0,0 +1,300 @@
|
||||
# Telegram Bot Uploader Backend Design
|
||||
|
||||
**Date:** 2026-05-17
|
||||
**Status:** Approved
|
||||
**Stack:** Bun, Telegraf, PostgreSQL, Drizzle ORM, Winston, nanoid
|
||||
|
||||
## Overview
|
||||
|
||||
Production-ready backend untuk Telegram file uploader dengan dual upload methods (bot + HTTP API), persistent storage di private Telegram channel, dan redirect-based download untuk minimal bandwidth usage.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Components
|
||||
|
||||
1. **HTTP Server** (Bun.serve)
|
||||
- Handle REST endpoints
|
||||
- Rate limiting (in-memory, per IP)
|
||||
- Request/response logging
|
||||
|
||||
2. **Telegram Bot** (Telegraf)
|
||||
- Listen file uploads from users
|
||||
- Forward files to private storage channel
|
||||
- Extract and persist metadata
|
||||
|
||||
3. **Database Layer** (Drizzle + PostgreSQL)
|
||||
- Persist file metadata
|
||||
- Indexed queries by public_id, telegram_file_id, uploader_id
|
||||
|
||||
### Data Flow
|
||||
|
||||
#### Upload via Telegram Bot
|
||||
1. User sends file to bot (document/photo/video/audio/voice/animation)
|
||||
2. Bot validates file size against Telegram limits
|
||||
3. Bot forwards file to private storage channel
|
||||
4. Bot extracts `telegram_file_id`, `telegram_file_unique_id`, `storage_message_id`
|
||||
5. Bot generates `public_id` using nanoid
|
||||
6. Bot saves metadata to PostgreSQL
|
||||
7. Bot replies with download link: `https://tele.asepharyana.tech/f/{public_id}`
|
||||
|
||||
#### Upload via HTTP API
|
||||
1. Client POSTs to `/api/upload` with file (multipart or base64)
|
||||
2. Server validates file size
|
||||
3. Server forwards file to private storage channel
|
||||
4. Server extracts file IDs and message ID
|
||||
5. Server generates `public_id`
|
||||
6. Server saves metadata to PostgreSQL
|
||||
7. Server returns full metadata JSON
|
||||
|
||||
#### Download (Redirect)
|
||||
1. User accesses `GET /f/:public_id`
|
||||
2. Server checks rate limit (30 req/min per IP, in-memory)
|
||||
3. Server queries DB by `public_id`
|
||||
4. Server calls Telegram Bot API `getFile()`
|
||||
5. Server redirects to Telegram CDN URL
|
||||
6. Browser downloads from Telegram (VPS bandwidth minimal)
|
||||
|
||||
#### File Info
|
||||
1. User accesses `GET /file/:public_id/info`
|
||||
2. Server queries DB by `public_id`
|
||||
3. Server returns full metadata (no rate limit)
|
||||
|
||||
## Database Schema
|
||||
|
||||
### Table: files
|
||||
|
||||
```sql
|
||||
CREATE TABLE files (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
public_id VARCHAR(21) UNIQUE NOT NULL,
|
||||
telegram_file_id VARCHAR NOT NULL,
|
||||
telegram_file_unique_id VARCHAR NOT NULL,
|
||||
storage_chat_id BIGINT NOT NULL,
|
||||
storage_message_id BIGINT NOT NULL,
|
||||
file_name VARCHAR NOT NULL,
|
||||
mime_type VARCHAR NOT NULL,
|
||||
size_bytes BIGINT NOT NULL,
|
||||
file_type VARCHAR NOT NULL,
|
||||
uploader_id BIGINT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_files_public_id ON files(public_id);
|
||||
CREATE INDEX idx_files_telegram_file_id ON files(telegram_file_id);
|
||||
CREATE INDEX idx_files_uploader_id ON files(uploader_id);
|
||||
CREATE INDEX idx_files_created_at ON files(created_at DESC);
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### POST /api/upload
|
||||
Upload file via HTTP API.
|
||||
|
||||
**Request (Multipart):**
|
||||
```
|
||||
Content-Type: multipart/form-data
|
||||
file: <binary>
|
||||
fileName: optional_filename.ext
|
||||
```
|
||||
|
||||
**Request (Base64):**
|
||||
```json
|
||||
{
|
||||
"file": "base64encodedstring",
|
||||
"fileName": "filename.ext"
|
||||
}
|
||||
```
|
||||
|
||||
**Response (200):**
|
||||
```json
|
||||
{
|
||||
"public_id": "abc123xyz",
|
||||
"file_name": "document.pdf",
|
||||
"mime_type": "application/pdf",
|
||||
"size_bytes": 1024000,
|
||||
"file_type": "document",
|
||||
"created_at": "2026-05-17T23:42:19Z",
|
||||
"download_url": "https://tele.asepharyana.tech/f/abc123xyz"
|
||||
}
|
||||
```
|
||||
|
||||
**Response (400):**
|
||||
```json
|
||||
{
|
||||
"error": "File size exceeds limit"
|
||||
}
|
||||
```
|
||||
|
||||
### GET /f/:public_id
|
||||
Redirect to Telegram CDN for download.
|
||||
|
||||
**Response (302):**
|
||||
```
|
||||
Location: https://api.telegram.org/file/bot.../...
|
||||
```
|
||||
|
||||
**Response (404):**
|
||||
```json
|
||||
{
|
||||
"error": "File not found"
|
||||
}
|
||||
```
|
||||
|
||||
**Response (429):**
|
||||
```json
|
||||
{
|
||||
"error": "Rate limit exceeded"
|
||||
}
|
||||
```
|
||||
|
||||
### GET /file/:public_id/info
|
||||
Get file metadata.
|
||||
|
||||
**Response (200):**
|
||||
```json
|
||||
{
|
||||
"public_id": "abc123xyz",
|
||||
"file_name": "document.pdf",
|
||||
"mime_type": "application/pdf",
|
||||
"size_bytes": 1024000,
|
||||
"file_type": "document",
|
||||
"uploader_id": 123456789,
|
||||
"created_at": "2026-05-17T23:42:19Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Response (404):**
|
||||
```json
|
||||
{
|
||||
"error": "File not found"
|
||||
}
|
||||
```
|
||||
|
||||
### GET /health
|
||||
Health check endpoint.
|
||||
|
||||
**Response (200):**
|
||||
```json
|
||||
{
|
||||
"status": "ok"
|
||||
}
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
src/
|
||||
index.js — entry point, start server & bot
|
||||
bot.js — Telegraf bot setup & handlers
|
||||
env.js — environment validation
|
||||
db/
|
||||
index.js — Drizzle setup & connection
|
||||
schema.js — Drizzle schema definition
|
||||
routes/
|
||||
files.js — GET /f/:public_id, GET /file/:public_id/info
|
||||
upload.js — POST /api/upload (multipart & base64)
|
||||
health.js — GET /health
|
||||
utils/
|
||||
file.js — file type validation, size check
|
||||
telegram.js — Telegram API helpers, forward file
|
||||
logger.js — Winston logger setup
|
||||
rateLimit.js — in-memory rate limiter
|
||||
package.json
|
||||
.env.example
|
||||
schema.sql
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
```
|
||||
BOT_TOKEN=<telegram_bot_token>
|
||||
STORAGE_CHANNEL_ID=<private_channel_id>
|
||||
BASE_URL=https://tele.asepharyana.tech
|
||||
DATABASE_URL=postgresql://user:password@localhost:5432/telegram_uploader
|
||||
PORT=3000
|
||||
NODE_ENV=production
|
||||
LOG_LEVEL=info
|
||||
RATE_LIMIT_WINDOW_MS=60000
|
||||
RATE_LIMIT_MAX_REQUESTS=30
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
- **Winston logger** with levels: error, warn, info, debug
|
||||
- **Structured logging** (JSON format) for production
|
||||
- **Try-catch** in all async operations
|
||||
- **Graceful error responses:**
|
||||
- 400 Bad Request — invalid input
|
||||
- 404 Not Found — file not found
|
||||
- 429 Too Many Requests — rate limit exceeded
|
||||
- 500 Internal Server Error — server error
|
||||
- **Telegram API errors** — log and retry logic for transient failures
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
- **In-memory rate limiter** per IP address
|
||||
- **Window-based:** reset every minute
|
||||
- **Limit:** 30 requests/minute per IP (configurable)
|
||||
- **Response:** 429 Too Many Requests if exceeded
|
||||
- **Applies to:** `GET /f/:public_id` only
|
||||
|
||||
## Graceful Shutdown
|
||||
|
||||
1. Stop accepting new requests
|
||||
2. Wait for pending requests to complete
|
||||
3. Close database connection pool
|
||||
4. Stop Telegram bot polling
|
||||
5. Exit process
|
||||
|
||||
## File Type Support
|
||||
|
||||
Accepts all Telegram file types:
|
||||
- document
|
||||
- photo
|
||||
- video
|
||||
- audio
|
||||
- voice
|
||||
- animation
|
||||
|
||||
File size limits per Telegram API:
|
||||
- Documents: 2GB
|
||||
- Photos: 10MB
|
||||
- Videos: 2GB
|
||||
- Audio: 200MB
|
||||
- Voice: 200MB
|
||||
- Animation: 2GB
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- No authentication on `/api/upload` (public endpoint)
|
||||
- Rate limiting on download endpoint to prevent abuse
|
||||
- File size validation against Telegram limits
|
||||
- Input validation on file names
|
||||
- SQL injection prevention via Drizzle ORM
|
||||
- CORS headers if needed for cross-origin requests
|
||||
|
||||
## Deployment Notes
|
||||
|
||||
- **Single instance:** in-memory rate limiter sufficient
|
||||
- **Multiple instances:** consider Redis-based rate limiter (future enhancement)
|
||||
- **Database:** PostgreSQL 12+
|
||||
- **Telegram Bot:** must be admin in private storage channel
|
||||
- **URL stability:** backend URL is permanent, Telegram CDN URLs may change
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
- Unit tests for utility functions (file validation, rate limiter)
|
||||
- Integration tests for database operations
|
||||
- E2E tests for upload and download flows
|
||||
- Manual testing with actual Telegram bot
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- Redis-based rate limiter for multi-instance deployments
|
||||
- File expiration/cleanup policies
|
||||
- Download analytics
|
||||
- API key authentication for `/api/upload`
|
||||
- Webhook support for upload notifications
|
||||
Reference in New Issue
Block a user