refactor(fase1b): unify upload/download via use-case + proxy download

- upload-file use-case is now the single save path with dedup policy
  (hash / bucket-key / none), partPrefix + signatureBuffer inputs,
  fileHash in UploadOutput, and owned temp-file cleanup; pass temp
  path (not open stream) to telegram service for testability
- upload-controller (multipart + JSON) and web-api upload delegate
  to the use-case; JSON body via JsonUploadPayloadSchema; responses
  built from use-case output via buildUploadResponse
- web-api download 302 redirect -> proxy stream (closes bot_token
  leak); shared CORS + sanitizeFilenameHeader
- file-controller drops double file-info cache layer (pool caches)
- bot handler uses DI singletons instead of direct construction
- get-file RedirectRetrieval.redirectUrl marked deprecated, URL via
  shared builder; chunked-storage URL via buildTelegramFileUrl
  (no inline api.telegram.org/file/bot left in src/)
This commit is contained in:
asepharyana
2026-09-14 18:03:37 +07:00
parent 7d5b8154a4
commit bd4cf8b285
8 changed files with 292 additions and 391 deletions
+6 -7
View File
@@ -4,8 +4,7 @@ import { buildNewFile } from '../../domain/entities/file-factory';
import type { IFileRepository } from '../../domain/ports/file-repository';
import type { ITelegramService } from '../../domain/ports/telegram-service';
import { config } from '../../env';
import { DrizzleFileRepository } from '../../infrastructure/persistence/repositories/file-repository';
import { botPool } from '../../infrastructure/telegram/bot-pool';
import { fileRepository, telegramService } from '../../infrastructure/di';
import logger from '../../shared/logger/index';
import {
checkFileSize,
@@ -75,9 +74,9 @@ const replyWithDownloadUrl = async (ctx: BotContext, publicId: string): Promise<
*
* @param deps - Optional external dependencies for testing or DI override.
* @param deps.telegramService - The Telegram service used to forward files to
* the storage channel. Defaults to the singleton BotPool instance.
* the storage channel. Defaults to the DI singleton.
* @param deps.fileRepo - The file repository used for deduplication queries
* and persisting new file records. Defaults to a new DrizzleFileRepository.
* and persisting new file records. Defaults to the DI singleton.
* @returns The launched Telegraf bot instance, suitable for graceful shutdown
* via `bot.stop(signal)`.
*/
@@ -89,8 +88,8 @@ export async function startBot(
fileRepo?: IFileRepository;
} = {},
): Promise<Telegraf<Context>> {
const telegramService = deps.telegramService ?? botPool;
const fileRepo = deps.fileRepo ?? new DrizzleFileRepository();
const telegramSvc = deps.telegramService ?? telegramService;
const fileRepo = deps.fileRepo ?? fileRepository;
try {
const bot = new Telegraf(config.botTokens[0]);
@@ -147,7 +146,7 @@ export async function startBot(
return;
}
const result = await telegramService.forwardToStorage(file_id, fileName, fileType);
const result = await telegramSvc.forwardToStorage(file_id, fileName, fileType);
const publicId = nanoid();
await fileRepo.create(