fix(storage): paginate stats query and align file size formatting (#3720)
Paginate Supabase select queries in the storage stats API to avoid the 1000 row default limit. Align StorageManager's formatFileSize with useQuotaStats calculation so quota values display consistently. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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 => {
|
||||
|
||||
@@ -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<string | null, { count: number; size: number }>();
|
||||
|
||||
allFiles.forEach((file) => {
|
||||
|
||||
Reference in New Issue
Block a user