fix: normalize web object sizes
This commit is contained in:
+9
-6
@@ -31,20 +31,21 @@ const mapDbRowToS3Record = (row: Record<string, unknown>): S3FileRecord => {
|
|||||||
publicId: row.public_id as string,
|
publicId: row.public_id as string,
|
||||||
telegramFileId: row.telegram_file_id as string,
|
telegramFileId: row.telegram_file_id as string,
|
||||||
telegramFileUniqueId: row.telegram_file_unique_id as string,
|
telegramFileUniqueId: row.telegram_file_unique_id as string,
|
||||||
storageChatId: row.storage_chat_id as number,
|
storageChatId: toNumber(row.storage_chat_id),
|
||||||
storageMessageId: row.storage_message_id as number,
|
storageMessageId: toNumber(row.storage_message_id),
|
||||||
fileName: row.file_name as string,
|
fileName: row.file_name as string,
|
||||||
mimeType: row.mime_type as string,
|
mimeType: row.mime_type as string,
|
||||||
sizeBytes: row.size_bytes as number,
|
sizeBytes: toNumber(row.size_bytes),
|
||||||
fileType: row.file_type as string,
|
fileType: row.file_type as string,
|
||||||
uploaderId: row.uploader_id as number,
|
uploaderId: toNumber(row.uploader_id),
|
||||||
fileHash: row.file_hash as string | null,
|
fileHash: row.file_hash as string | null,
|
||||||
archiveTelegramFileId: row.archive_telegram_file_id as string | null,
|
archiveTelegramFileId: row.archive_telegram_file_id as string | null,
|
||||||
archiveStorageMessageId: row.archive_storage_message_id as number | null,
|
archiveStorageMessageId:
|
||||||
|
row.archive_storage_message_id === null ? null : toNumber(row.archive_storage_message_id),
|
||||||
archiveFileName: row.archive_file_name as string | null,
|
archiveFileName: row.archive_file_name as string | null,
|
||||||
archiveEntryName: row.archive_entry_name as string | null,
|
archiveEntryName: row.archive_entry_name as string | null,
|
||||||
archiveMimeType: row.archive_mime_type as string | null,
|
archiveMimeType: row.archive_mime_type as string | null,
|
||||||
archiveSizeBytes: row.archive_size_bytes as number | null,
|
archiveSizeBytes: row.archive_size_bytes === null ? null : toNumber(row.archive_size_bytes),
|
||||||
bucketId: row.bucket_id as string,
|
bucketId: row.bucket_id as string,
|
||||||
s3Key: row.s3_key as string,
|
s3Key: row.s3_key as string,
|
||||||
storageBackend: (row.storage_backend as string) || 'telegram',
|
storageBackend: (row.storage_backend as string) || 'telegram',
|
||||||
@@ -57,6 +58,8 @@ const mapDbRowToS3Record = (row: Record<string, unknown>): S3FileRecord => {
|
|||||||
|
|
||||||
const escapeLike = (s: string): string => s.replace(/[%_\\]/g, '\\$&');
|
const escapeLike = (s: string): string => s.replace(/[%_\\]/g, '\\$&');
|
||||||
|
|
||||||
|
const toNumber = (value: unknown): number => Number(value ?? 0);
|
||||||
|
|
||||||
export const listObjectsByPrefix = async (
|
export const listObjectsByPrefix = async (
|
||||||
bucketId: string,
|
bucketId: string,
|
||||||
prefix: string,
|
prefix: string,
|
||||||
|
|||||||
+2
-1
@@ -90,7 +90,8 @@ export const config: AppConfig = {
|
|||||||
s3DefaultRegion: process.env.S3_DEFAULT_REGION || 'us-east-1',
|
s3DefaultRegion: process.env.S3_DEFAULT_REGION || 'us-east-1',
|
||||||
proxyS3Get: process.env.PROXY_S3_GET !== 'false',
|
proxyS3Get: process.env.PROXY_S3_GET !== 'false',
|
||||||
s3VhostDomains: parseDomains(
|
s3VhostDomains: parseDomains(
|
||||||
process.env.S3_VHOST_DOMAINS || 'upload.asepharyana.my.id,asepharyana.web.id,upload.asepharyana.web.id',
|
process.env.S3_VHOST_DOMAINS ||
|
||||||
|
'upload.asepharyana.my.id,asepharyana.web.id,upload.asepharyana.web.id',
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -202,7 +202,7 @@
|
|||||||
}
|
}
|
||||||
container.innerHTML = html;
|
container.innerHTML = html;
|
||||||
};
|
};
|
||||||
const formatSize = (bytes) => { if (!bytes) return '0 B'; const u = ['B','KB','MB','GB','TB']; let i=0,s=bytes; while(s>=1024&&i<u.length-1){s/=1024;i++} return `${s.toFixed(i>0?1:0)} ${u[i]}`; };
|
const formatSize = (bytes) => { const size = Number(bytes); if (!Number.isFinite(size) || size <= 0) return '0 B'; const u = ['B','KB','MB','GB','TB']; let i=0,s=size; while(s>=1024&&i<u.length-1){s/=1024;i++} return `${s.toFixed(i>0?1:0)} ${u[i]}`; };
|
||||||
const formatDate = (iso) => { if(!iso)return ''; return new Date(iso).toLocaleDateString(undefined,{month:'short',day:'numeric',year:'numeric'}); };
|
const formatDate = (iso) => { if(!iso)return ''; return new Date(iso).toLocaleDateString(undefined,{month:'short',day:'numeric',year:'numeric'}); };
|
||||||
const escapeHtml = (s) => { const d=document.createElement('div');d.textContent=s;return d.innerHTML; };
|
const escapeHtml = (s) => { const d=document.createElement('div');d.textContent=s;return d.innerHTML; };
|
||||||
const debouncedSearch = () => { clearTimeout(searchTimer); searchTimer = setTimeout(loadObjects, 300); };
|
const debouncedSearch = () => { clearTimeout(searchTimer); searchTimer = setTimeout(loadObjects, 300); };
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export const handleListObjectsV1 = async (req: Request, params: RouteParams): Pr
|
|||||||
key: o.s3Key,
|
key: o.s3Key,
|
||||||
fileName: o.fileName,
|
fileName: o.fileName,
|
||||||
mimeType: o.mimeType,
|
mimeType: o.mimeType,
|
||||||
sizeBytes: o.sizeBytes,
|
sizeBytes: Number(o.sizeBytes),
|
||||||
fileType: o.fileType,
|
fileType: o.fileType,
|
||||||
etag: o.fileHash,
|
etag: o.fileHash,
|
||||||
lastModified:
|
lastModified:
|
||||||
|
|||||||
+33
-2
@@ -1,4 +1,4 @@
|
|||||||
import { afterAll, beforeAll, describe, expect, it, mock } from 'bun:test';
|
import { afterAll, beforeAll, beforeEach, describe, expect, it, mock } from 'bun:test';
|
||||||
|
|
||||||
const mockBuckets = [
|
const mockBuckets = [
|
||||||
{
|
{
|
||||||
@@ -9,6 +9,9 @@ const mockBuckets = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
let mockObjects: Record<string, unknown>[] = [];
|
||||||
|
let mockPrefixes: string[] = [];
|
||||||
|
|
||||||
mock.module('../src/db/buckets', () => ({
|
mock.module('../src/db/buckets', () => ({
|
||||||
listBuckets: () => Promise.resolve(mockBuckets),
|
listBuckets: () => Promise.resolve(mockBuckets),
|
||||||
findBucketByName: (name: string) =>
|
findBucketByName: (name: string) =>
|
||||||
@@ -21,7 +24,7 @@ mock.module('../src/db/buckets', () => ({
|
|||||||
|
|
||||||
mock.module('../src/db/files-ext', () => ({
|
mock.module('../src/db/files-ext', () => ({
|
||||||
findFileByBucketAndKey: () => Promise.resolve(null),
|
findFileByBucketAndKey: () => Promise.resolve(null),
|
||||||
listObjectsByPrefix: () => Promise.resolve({ objects: [], prefixes: [] }),
|
listObjectsByPrefix: () => Promise.resolve({ objects: mockObjects, prefixes: mockPrefixes }),
|
||||||
softDeleteFile: () => Promise.resolve(true),
|
softDeleteFile: () => Promise.resolve(true),
|
||||||
softDeleteFilesBatch: () => Promise.resolve(0),
|
softDeleteFilesBatch: () => Promise.resolve(0),
|
||||||
countBucketObjects: () => Promise.resolve(0),
|
countBucketObjects: () => Promise.resolve(0),
|
||||||
@@ -56,6 +59,11 @@ describe('Web API v1', () => {
|
|||||||
handleWebApiV1 = webApi.handleWebApiV1;
|
handleWebApiV1 = webApi.handleWebApiV1;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockObjects = [];
|
||||||
|
mockPrefixes = [];
|
||||||
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
mock.restore();
|
mock.restore();
|
||||||
});
|
});
|
||||||
@@ -89,4 +97,27 @@ describe('Web API v1', () => {
|
|||||||
const data = (await res.json()) as { error: string };
|
const data = (await res.json()) as { error: string };
|
||||||
expect(data.error).toContain('Invalid bucket name');
|
expect(data.error).toContain('Invalid bucket name');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should normalize listed object sizeBytes to a number', async () => {
|
||||||
|
mockObjects = [
|
||||||
|
{
|
||||||
|
s3Key: 'tiny.txt',
|
||||||
|
fileName: 'tiny.txt',
|
||||||
|
mimeType: 'text/plain',
|
||||||
|
sizeBytes: '12',
|
||||||
|
fileType: 'document',
|
||||||
|
fileHash: 'etag',
|
||||||
|
createdAt: new Date('2026-01-01T00:00:00Z'),
|
||||||
|
publicId: 'public-id',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const req = new Request('http://localhost:3000/api/v1/buckets/test-bucket/objects');
|
||||||
|
const res = await handleWebApiV1(req);
|
||||||
|
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
const data = (await res.json()) as { objects: { sizeBytes: unknown }[] };
|
||||||
|
expect(data.objects[0].sizeBytes).toBe(12);
|
||||||
|
expect(typeof data.objects[0].sizeBytes).toBe('number');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user