From 34597b99093d43e2862060820f600a6e2723dcbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20M=C3=BCllner?= Date: Thu, 5 Mar 2026 14:13:26 +0100 Subject: [PATCH] fix: use exact collection stats beyond row limits --- lib/screens/garage_screen.dart | 20 +----- lib/services/collection_service.dart | 95 ++++++++++++++++++++++++---- 2 files changed, 87 insertions(+), 28 deletions(-) diff --git a/lib/screens/garage_screen.dart b/lib/screens/garage_screen.dart index cfd9674..659d808 100644 --- a/lib/screens/garage_screen.dart +++ b/lib/screens/garage_screen.dart @@ -139,26 +139,12 @@ class GarageScreenState extends State { Future _loadCollectionStats() async { try { - final weekAgoIso = DateTime.now() - .subtract(const Duration(days: 7)) - .toUtc() - .toIso8601String(); - - final totalResult = await supabase - .from('hotwheels') - .select('id') - .eq('collection_id', widget.collectionId); - - final recentResult = await supabase - .from('hotwheels') - .select('id') - .eq('collection_id', widget.collectionId) - .gte('created_at', weekAgoIso); + final stats = await CollectionService.getCollectionStats(widget.collectionId); if (!mounted) return; setState(() { - _totalCarsCount = totalResult.length; - _recentCarsCount = recentResult.length; + _totalCarsCount = stats.total; + _recentCarsCount = stats.recent; }); } catch (_) { if (!mounted) return; diff --git a/lib/services/collection_service.dart b/lib/services/collection_service.dart index 4766f55..a38105c 100644 --- a/lib/services/collection_service.dart +++ b/lib/services/collection_service.dart @@ -103,17 +103,7 @@ class CollectionService { final collectionIdList = collectionIds.toList(); - // Fetch all items for these collections in a single query and count them in memory. - final items = await supabase - .from('hotwheels') - .select('id, collection_id') - .inFilter('collection_id', collectionIdList); - - final itemCounts = {}; - for (final item in items) { - final collectionId = item['collection_id'] as String; - itemCounts[collectionId] = (itemCounts[collectionId] ?? 0) + 1; - } + final itemCounts = await getCollectionItemCounts(collectionIdList); final memberCounts = {}; await Future.wait(collectionIdList.map((collectionId) async { @@ -171,6 +161,89 @@ class CollectionService { return collections; } + static Future> getCollectionItemCounts( + List collectionIds) async { + if (collectionIds.isEmpty) return {}; + + final itemCounts = {}; + try { + final rows = await supabase.rpc('get_collection_counts', params: { + 'p_collection_ids': collectionIds, + }); + + for (final row in rows as List) { + final collectionId = row['collection_id'] as String?; + if (collectionId == null) continue; + itemCounts[collectionId] = (row['total_count'] as num?)?.toInt() ?? 0; + } + } catch (_) {} + + final unresolvedIds = collectionIds + .where((collectionId) => !itemCounts.containsKey(collectionId)) + .toList(growable: false); + + if (unresolvedIds.isNotEmpty) { + await Future.wait(unresolvedIds.map((collectionId) async { + itemCounts[collectionId] = await _countCarsPaginated(collectionId); + })); + } + + return itemCounts; + } + + static Future<({int total, int recent})> getCollectionStats( + String collectionId) async { + try { + final rows = await supabase.rpc('get_collection_counts', params: { + 'p_collection_ids': [collectionId], + }); + + if (rows is List && rows.isNotEmpty) { + final first = rows.first as Map; + return ( + total: (first['total_count'] as num?)?.toInt() ?? 0, + recent: (first['recent_count'] as num?)?.toInt() ?? 0, + ); + } + } catch (_) {} + + final weekAgoIso = DateTime.now() + .subtract(const Duration(days: 7)) + .toUtc() + .toIso8601String(); + final total = await _countCarsPaginated(collectionId); + final recent = await _countCarsPaginated(collectionId, sinceIso: weekAgoIso); + return (total: total, recent: recent); + } + + static Future _countCarsPaginated(String collectionId, + {String? sinceIso}) async { + const pageSize = 1000; + var from = 0; + var total = 0; + + while (true) { + dynamic query = supabase + .from('hotwheels') + .select('id') + .eq('collection_id', collectionId) + .range(from, from + pageSize - 1); + + if (sinceIso != null) { + query = query.gte('created_at', sinceIso); + } + + final rows = await query; + final count = (rows as List).length; + total += count; + + if (count < pageSize) break; + from += pageSize; + } + + return total; + } + /// Create a new collection. The caller is automatically added as owner. static Future create({ required String name,