diff --git a/apps/readest-app/src/app/user/components/StorageManager.tsx b/apps/readest-app/src/app/user/components/StorageManager.tsx index dc09acf8..71453acf 100644 --- a/apps/readest-app/src/app/user/components/StorageManager.tsx +++ b/apps/readest-app/src/app/user/components/StorageManager.tsx @@ -241,10 +241,11 @@ const StorageManager = () => { const formatFileSize = (bytes: number): string => { if (bytes === 0) return '0 B'; - const k = 1024; - const sizes = ['B', 'KB', 'MB', 'GB']; - const i = Math.floor(Math.log(bytes) / Math.log(k)); - return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`; + if (bytes < 1024) return `${bytes} B`; + if (bytes < 1e6) return `${Math.round((bytes / 1024) * 10) / 10} KB`; + const inGB = bytes > 1e9; + const value = bytes / 1024 / 1024 / (inGB ? 1024 : 1); + return `${Math.round(value * 10) / 10} ${inGB ? 'GB' : 'MB'}`; }; const formatDate = (dateString: string): string => { diff --git a/apps/readest-app/src/pages/api/storage/stats.ts b/apps/readest-app/src/pages/api/storage/stats.ts index d84509b9..e6dd6a31 100644 --- a/apps/readest-app/src/pages/api/storage/stats.ts +++ b/apps/readest-app/src/pages/api/storage/stats.ts @@ -31,20 +31,36 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) const supabase = createSupabaseAdminClient(); - // Get total file count and size - const { data: totalStats, error: totalError } = await supabase - .from('files') - .select('file_size') - .eq('user_id', user.id) - .is('deleted_at', null); + // Get total file count and size (paginated to avoid Supabase 1000 row limit) + const PAGE_SIZE = 1000; + let allFileStats: { file_size: number }[] = []; + let offset = 0; + let hasMore = true; - if (totalError) { - console.error('Error querying total stats:', totalError); - return res.status(500).json({ error: 'Failed to retrieve storage statistics' }); + while (hasMore) { + const { data, error } = await supabase + .from('files') + .select('file_size') + .eq('user_id', user.id) + .is('deleted_at', null) + .range(offset, offset + PAGE_SIZE - 1); + + if (error) { + console.error('Error querying total stats:', error); + return res.status(500).json({ error: 'Failed to retrieve storage statistics' }); + } + + if (data && data.length > 0) { + allFileStats = allFileStats.concat(data); + offset += PAGE_SIZE; + hasMore = data.length === PAGE_SIZE; + } else { + hasMore = false; + } } - const totalFiles = totalStats?.length || 0; - const totalSize = totalStats?.reduce((sum, file) => sum + (file.file_size || 0), 0) || 0; + const totalFiles = allFileStats.length; + const totalSize = allFileStats.reduce((sum, file) => sum + (file.file_size || 0), 0); // Get storage plan data const { usage, quota } = getStoragePlanData(token); @@ -62,13 +78,30 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) if (bookHashError) { console.warn('RPC function not available, using fallback aggregation:', bookHashError); - const { data: allFiles, error: filesError } = await supabase - .from('files') - .select('book_hash, file_size') - .eq('user_id', user.id) - .is('deleted_at', null); + let allFiles: { book_hash: string | null; file_size: number }[] = []; + let fallbackOffset = 0; + let fallbackHasMore = true; - if (!filesError && allFiles) { + while (fallbackHasMore) { + const { data, error: filesError } = await supabase + .from('files') + .select('book_hash, file_size') + .eq('user_id', user.id) + .is('deleted_at', null) + .range(fallbackOffset, fallbackOffset + PAGE_SIZE - 1); + + if (filesError) break; + + if (data && data.length > 0) { + allFiles = allFiles.concat(data); + fallbackOffset += PAGE_SIZE; + fallbackHasMore = data.length === PAGE_SIZE; + } else { + fallbackHasMore = false; + } + } + + if (allFiles.length > 0) { const grouped = new Map(); allFiles.forEach((file) => {