From 8508b360268f649e19763d1fb8b02f95b2fbe593 Mon Sep 17 00:00:00 2001 From: MythEclipse Date: Fri, 29 May 2026 03:43:49 +0700 Subject: [PATCH] feat: update file handling to redirect to Telegram CDN and adjust Swagger documentation --- src/routes/files.ts | 15 ++++----------- src/routes/swagger.ts | 6 +++--- test/files.test.ts | 19 +++++-------------- test/rateLimit.test.ts | 30 ++++++++---------------------- test/swagger.test.ts | 6 +++--- 5 files changed, 23 insertions(+), 53 deletions(-) diff --git a/src/routes/files.ts b/src/routes/files.ts index 01c8ed5..401899c 100644 --- a/src/routes/files.ts +++ b/src/routes/files.ts @@ -109,19 +109,12 @@ export const handleFileRedirect = async (req: RequestWithParams): Promise => { '/f/{public_id}': { get: { summary: 'Download File', - description: 'Proxies file from Telegram storage as a streamed download. Rate-limited by IP.', + description: 'Redirects to Telegram CDN for direct download. Rate-limited by IP.', parameters: [publicIdParameter], responses: { - '200': { - description: 'File binary stream.', + '302': { + description: 'Redirect to Telegram CDN URL.', }, '404': { description: 'File not found.', diff --git a/test/files.test.ts b/test/files.test.ts index 5c5e1d5..fefcc0a 100644 --- a/test/files.test.ts +++ b/test/files.test.ts @@ -120,7 +120,7 @@ describe('File Route Handlers', () => { expect(body.error).toBe('File not found'); }); - it('should proxy download (200 stream) instead of 302 redirect', async () => { + it('should redirect to telegram file url with 302', async () => { mockSelect.mockImplementationOnce(() => ({ from: () => ({ where: () => ({ @@ -142,19 +142,10 @@ describe('File Route Handlers', () => { const req = requestWithPublicId('http://localhost:3000/f/test-id', 'test-id'); const res = await handleFileRedirect(req); - // No longer 302 redirect - expect(res.status).toBe(200); - - // No Location header with token - expect(res.headers.get('Location')).toBeNull(); - - // Should have Content-Disposition - const disposition = res.headers.get('Content-Disposition'); - expect(disposition).toBeTruthy(); - expect(disposition).toContain('test.jpg'); - - // fetch should have been called for the proxy - expect(mockGlobalFetch).toHaveBeenCalled(); + expect(res.status).toBe(302); + expect(res.headers.get('Location')).toBe( + 'https://api.telegram.org/file/bot123456:ABC-DEF/photos/file_0.jpg', + ); }); it('should return 500 on database or external errors', async () => { diff --git a/test/rateLimit.test.ts b/test/rateLimit.test.ts index 6c441ef..f641e56 100644 --- a/test/rateLimit.test.ts +++ b/test/rateLimit.test.ts @@ -1,4 +1,5 @@ import { beforeEach, describe, expect, it } from 'bun:test'; +import { config } from '../src/env'; import { checkRateLimit, cleanupRateLimitCache, clearRateLimitCache } from '../src/utils/rateLimit'; describe('Rate Limiter', () => { @@ -8,38 +9,23 @@ describe('Rate Limiter', () => { it('should allow requests up to the configured limit then block', () => { const key = 'user-1'; + const limit = config.rateLimitMaxRequests; - // Default config maxRequests is 30; all 20 should pass - for (let i = 0; i < 20; i++) { + for (let i = 0; i < limit; i++) { expect(checkRateLimit(key)).toBe(true); } - }); - - it('should block requests when limit exceeded', () => { - const key = 'user-2'; - - // Exhaust the limit (30 by default) - for (let i = 0; i < 30; i++) { - checkRateLimit(key); - } expect(checkRateLimit(key)).toBe(false); }); - it('should reset window after cleanup on expired entries', async () => { - const key = 'user-3'; - - // Use one request then wait past the window - expect(checkRateLimit(key)).toBe(true); - - // Simulate expiry by advancing past the window - // We can only test cleanup of non-expired entries (no-op) - expect(() => cleanupRateLimitCache()).not.toThrow(); - }); - it('should track different IPs independently', () => { expect(checkRateLimit('10.0.0.1')).toBe(true); expect(checkRateLimit('10.0.0.1')).toBe(true); expect(checkRateLimit('10.0.0.2')).toBe(true); }); + + it('should no-op on cleanup of active entries', () => { + checkRateLimit('user-3'); + expect(() => cleanupRateLimitCache()).not.toThrow(); + }); }); diff --git a/test/swagger.test.ts b/test/swagger.test.ts index 96ba189..1c746ed 100644 --- a/test/swagger.test.ts +++ b/test/swagger.test.ts @@ -30,10 +30,10 @@ describe('Swagger Documentation Endpoints', () => { const uploadResponses = uploadPath.post.responses; expect(uploadResponses).toHaveProperty('429'); - // Verify download is no longer documented as 302 redirect + // Verify download is 302 redirect to Telegram CDN const downloadResponses = downloadPath.get.responses; - expect(downloadResponses['200'].description).toContain('stream'); - expect(downloadResponses).not.toHaveProperty('302'); + expect(downloadResponses).toHaveProperty('302'); + expect(downloadResponses['302'].description).toContain('Redirect'); }); it('returns Swagger UI HTML page', async () => {