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
+24 -2
View File
@@ -1,3 +1,11 @@
/**
* Deduplication policy for the upload file use case.
* - `hash`: look up an existing record by content SHA-256 first (default).
* - `bucket-key`: idempotent lookup by `(bucketId, s3Key)`; never re-uploads.
* - `none`: store unconditionally.
*/
export type UploadDedupPolicy = 'hash' | 'bucket-key' | 'none';
/**
* Input for the upload file use case.
* Carries all metadata needed to persist an uploaded file,
@@ -12,8 +20,11 @@ export interface UploadInput {
fileName: string;
/** MIME type detected from content inspection or request header */
mimeType: string;
/** High-level file category (e.g. "document", "photo", "video") */
fileType: string;
/**
* High-level file category (e.g. "document", "photo", "video").
* Optional — when omitted the use case derives it from MIME type + file name.
*/
fileType?: string;
/** File size in bytes */
sizeBytes: number;
/** Telegram user ID of the uploader; 0 when unknown or system */
@@ -22,6 +33,15 @@ export interface UploadInput {
bucketId?: string | null;
/** Object key within the bucket for S3-compatible storage; null when un-bucketed */
s3Key?: string | null;
/** Deduplication policy; defaults to `'hash'` when omitted */
dedup?: UploadDedupPolicy;
/** Prefix for chunked part file names; defaults to `direct-<hash16>` */
partPrefix?: string;
/**
* First bytes of the file for magic-byte detection.
* When omitted the use case reads them from `tempPath`.
*/
signatureBuffer?: Buffer;
}
/**
@@ -43,4 +63,6 @@ export interface UploadOutput {
createdAt: Date;
/** Public download URL */
downloadUrl: string;
/** SHA-256 hex digest of the stored content */
fileHash: string | null;
}