feat: dedup wave 2 — file factory + V1/V2 mapping + bot handler
Deploy FileDrop / deploy (push) Successful in 42s

-  buildNewFile() factory (src/domain/entities/file-factory.ts)
  eliminates ~380 lines of archive null / isDeleted / field defaults
- ♻️ 27 creation blocks → buildNewFile() across 8 files
- ♻️ S3 V1/V2 object listing mapping → mapFileToListEntry()
- ♻️ Bot handler size validation → checkFileSize() from shared utils
- 🔎 computeHash audit: all 10 instances are streaming (no dedup)
- Lint  Build  Push 
This commit is contained in:
Claude
2026-07-29 16:02:01 +07:00
parent 332853f398
commit 3501d547c0
9 changed files with 364 additions and 327 deletions
+18 -25
View File
@@ -1,4 +1,5 @@
import { nanoid } from 'nanoid';
import { buildNewFile } from '../../domain/entities/file-factory';
import type { MultipartUpload } from '../../domain/entities/multipart';
import type { IBucketRepository } from '../../domain/ports/bucket-repository';
import type { IFileRepository } from '../../domain/ports/file-repository';
@@ -270,31 +271,23 @@ export function createCompleteMultipartUploadUseCase(deps: MultipartDeps) {
const publicId = nanoid();
await deps.fileRepo.create({
publicId,
telegramFileId: firstPart.telegramFileId,
telegramFileUniqueId: firstPart.telegramFileUniqueId,
storageChatId: deps.config.storageChatId,
storageMessageId: firstPart.storageMessageId,
fileName: input.key.split('/').pop() || 'file',
mimeType: 'application/octet-stream',
sizeBytes: totalSize,
fileType: 'document',
uploaderId: 0,
fileHash: null,
archiveTelegramFileId: null,
archiveStorageMessageId: null,
archiveFileName: null,
archiveEntryName: null,
archiveMimeType: null,
archiveSizeBytes: null,
bucketId: multipart.bucketId,
s3Key: input.key,
storageBackend: 'telegram',
isDeleted: false,
multipartUploadId: input.uploadId,
partCount: null,
});
await deps.fileRepo.create(
buildNewFile({
publicId,
telegramFileId: firstPart.telegramFileId,
telegramFileUniqueId: firstPart.telegramFileUniqueId,
storageChatId: deps.config.storageChatId,
storageMessageId: firstPart.storageMessageId,
fileName: input.key.split('/').pop() || 'file',
mimeType: 'application/octet-stream',
sizeBytes: totalSize,
fileType: 'document',
storageBackend: 'telegram',
bucketId: multipart.bucketId,
s3Key: input.key,
multipartUploadId: input.uploadId,
}),
);
await deps.multipartRepo.complete(input.uploadId);
+61 -75
View File
@@ -2,6 +2,7 @@ import { randomUUID } from 'node:crypto';
import { gzipSync } from 'node:zlib';
import { nanoid } from 'nanoid';
import type { File } from '../../domain/entities/file';
import { buildNewFile } from '../../domain/entities/file-factory';
import type { NewFilePart } from '../../domain/entities/file-part';
import type { MultipartPart } from '../../domain/entities/multipart';
import type { IBucketRepository } from '../../domain/ports/bucket-repository';
@@ -482,31 +483,24 @@ export function createPutObjectUseCase(deps: S3ObjectDeps) {
const fileId = randomUUID();
const publicId = nanoid();
await deps.fileRepo.create({
publicId,
telegramFileId: firstPart.telegramFileId,
telegramFileUniqueId: firstPart.telegramFileUniqueId,
storageChatId,
storageMessageId: firstPart.storageMessageId,
fileName: finalFileName,
mimeType,
sizeBytes: chunkResult.totalSizeBytes,
fileType: 'document',
uploaderId: 0,
fileHash: chunkResult.fileHash,
archiveTelegramFileId: null,
archiveStorageMessageId: null,
archiveFileName: null,
archiveEntryName: null,
archiveMimeType: null,
archiveSizeBytes: null,
bucketId: bucket.id,
s3Key: key,
storageBackend: 'chunked',
isDeleted: false,
multipartUploadId: null,
partCount: chunkResult.parts.length,
});
await deps.fileRepo.create(
buildNewFile({
publicId,
telegramFileId: firstPart.telegramFileId,
telegramFileUniqueId: firstPart.telegramFileUniqueId,
storageChatId,
storageMessageId: firstPart.storageMessageId,
fileName: finalFileName,
mimeType,
sizeBytes: chunkResult.totalSizeBytes,
fileType: 'document',
fileHash: chunkResult.fileHash,
bucketId: bucket.id,
s3Key: key,
storageBackend: 'chunked',
partCount: chunkResult.parts.length,
}),
);
const fileParts: NewFilePart[] = chunkResult.parts.map((part) => ({
fileId,
@@ -535,31 +529,24 @@ export function createPutObjectUseCase(deps: S3ObjectDeps) {
const publicId = nanoid();
await deps.fileRepo.create({
publicId,
telegramFileId: forwardResult.telegramFileId,
telegramFileUniqueId: forwardResult.telegramFileUniqueId,
storageChatId,
storageMessageId: forwardResult.storageMessageId,
fileName: finalFileName,
mimeType,
sizeBytes: body.byteLength,
fileType: 'document',
uploaderId: 0,
fileHash: hash,
archiveTelegramFileId: null,
archiveStorageMessageId: null,
archiveFileName: null,
archiveEntryName: null,
archiveMimeType: null,
archiveSizeBytes: null,
bucketId: bucket.id,
s3Key: key,
storageBackend: 'telegram',
isDeleted: false,
multipartUploadId: null,
partCount: null,
});
await deps.fileRepo.create(
buildNewFile({
publicId,
telegramFileId: forwardResult.telegramFileId,
telegramFileUniqueId: forwardResult.telegramFileUniqueId,
storageChatId,
storageMessageId: forwardResult.storageMessageId,
fileName: finalFileName,
mimeType,
sizeBytes: body.byteLength,
fileType: 'document',
fileHash: hash,
bucketId: bucket.id,
s3Key: key,
storageBackend: 'telegram',
partCount: null,
}),
);
return { etag: `"${hash}"` };
};
@@ -625,31 +612,30 @@ export function createCopyObjectUseCase(deps: S3ObjectDeps) {
const publicId = nanoid();
await deps.fileRepo.create({
publicId,
telegramFileId: sourceFile.telegramFileId,
telegramFileUniqueId: sourceFile.telegramFileUniqueId,
storageChatId: sourceFile.storageChatId,
storageMessageId: sourceFile.storageMessageId,
fileName: sourceFile.fileName,
mimeType: sourceFile.mimeType,
sizeBytes: sourceFile.sizeBytes,
fileType: sourceFile.fileType,
uploaderId: 0,
fileHash: sourceFile.fileHash,
archiveTelegramFileId: sourceFile.archiveTelegramFileId,
archiveStorageMessageId: sourceFile.archiveStorageMessageId,
archiveFileName: sourceFile.archiveFileName,
archiveEntryName: sourceFile.archiveEntryName,
archiveMimeType: sourceFile.archiveMimeType,
archiveSizeBytes: sourceFile.archiveSizeBytes,
bucketId: input.destBucketId,
s3Key: input.destKey,
storageBackend: 'telegram',
isDeleted: false,
multipartUploadId: null,
partCount: null,
});
await deps.fileRepo.create(
buildNewFile({
publicId,
telegramFileId: sourceFile.telegramFileId,
telegramFileUniqueId: sourceFile.telegramFileUniqueId,
storageChatId: sourceFile.storageChatId,
storageMessageId: sourceFile.storageMessageId,
fileName: sourceFile.fileName,
mimeType: sourceFile.mimeType,
sizeBytes: sourceFile.sizeBytes,
fileType: sourceFile.fileType,
fileHash: sourceFile.fileHash,
archiveTelegramFileId: sourceFile.archiveTelegramFileId,
archiveStorageMessageId: sourceFile.archiveStorageMessageId,
archiveFileName: sourceFile.archiveFileName,
archiveEntryName: sourceFile.archiveEntryName,
archiveMimeType: sourceFile.archiveMimeType,
archiveSizeBytes: sourceFile.archiveSizeBytes,
bucketId: input.destBucketId,
s3Key: input.destKey,
storageBackend: 'telegram',
partCount: null,
}),
);
return {
etag: sourceEtag || nanoid(16),
+38 -50
View File
@@ -1,6 +1,7 @@
import { createReadStream } from 'node:fs';
import { open } from 'node:fs/promises';
import { nanoid } from 'nanoid';
import { buildNewFile } from '../../domain/entities/file-factory';
import type { NewFilePart } from '../../domain/entities/file-part';
import type { IFilePartRepository } from '../../domain/ports/file-part-repository';
import type { IFileRepository } from '../../domain/ports/file-repository';
@@ -231,31 +232,25 @@ export function createUploadFileUseCase(deps: UploadFileUseCaseDeps) {
const fileId = nanoid();
const publicId = nanoid();
const newFile = await deps.fileRepo.create({
publicId,
telegramFileId: firstPart.telegramFileId,
telegramFileUniqueId: firstPart.telegramFileUniqueId,
storageChatId: deps.config.storageChatId,
storageMessageId: firstPart.storageMessageId,
fileName: finalFileName,
mimeType,
sizeBytes: chunkResult.totalSizeBytes,
fileType,
uploaderId: input.uploaderId ?? 0,
fileHash: chunkResult.fileHash,
archiveTelegramFileId: null,
archiveStorageMessageId: null,
archiveFileName: null,
archiveEntryName: null,
archiveMimeType: null,
archiveSizeBytes: null,
bucketId: input.bucketId ?? null,
s3Key: input.s3Key ?? null,
storageBackend: 'chunked',
isDeleted: false,
multipartUploadId: null,
partCount: chunkResult.parts.length,
});
const newFile = await deps.fileRepo.create(
buildNewFile({
publicId,
telegramFileId: firstPart.telegramFileId,
telegramFileUniqueId: firstPart.telegramFileUniqueId,
storageChatId: deps.config.storageChatId,
storageMessageId: firstPart.storageMessageId,
fileName: finalFileName,
mimeType,
sizeBytes: chunkResult.totalSizeBytes,
fileType,
storageBackend: 'chunked',
uploaderId: input.uploaderId,
fileHash: chunkResult.fileHash,
bucketId: input.bucketId,
s3Key: input.s3Key,
partCount: chunkResult.parts.length,
}),
);
const fileParts: NewFilePart[] = chunkResult.parts.map((part) => ({
fileId,
@@ -292,31 +287,24 @@ export function createUploadFileUseCase(deps: UploadFileUseCaseDeps) {
const singlePublicId = nanoid();
const createdFile = await deps.fileRepo.create({
publicId: singlePublicId,
telegramFileId: forwardResult.telegramFileId,
telegramFileUniqueId: forwardResult.telegramFileUniqueId,
storageChatId: deps.config.storageChatId,
storageMessageId: forwardResult.storageMessageId,
fileName: finalFileName,
mimeType,
sizeBytes: input.sizeBytes,
fileType,
uploaderId: input.uploaderId ?? 0,
fileHash: input.fileHash,
archiveTelegramFileId: null,
archiveStorageMessageId: null,
archiveFileName: null,
archiveEntryName: null,
archiveMimeType: null,
archiveSizeBytes: null,
bucketId: input.bucketId ?? null,
s3Key: input.s3Key ?? null,
storageBackend: 'telegram',
isDeleted: false,
multipartUploadId: null,
partCount: null,
});
const createdFile = await deps.fileRepo.create(
buildNewFile({
publicId: singlePublicId,
telegramFileId: forwardResult.telegramFileId,
telegramFileUniqueId: forwardResult.telegramFileUniqueId,
storageChatId: deps.config.storageChatId,
storageMessageId: forwardResult.storageMessageId,
fileName: finalFileName,
mimeType,
sizeBytes: input.sizeBytes,
fileType,
storageBackend: 'telegram',
uploaderId: input.uploaderId,
fileHash: input.fileHash,
bucketId: input.bucketId,
s3Key: input.s3Key,
}),
);
return {
publicId: createdFile.publicId,