fix: use exact collection stats beyond row limits

This commit is contained in:
Lukas Müllner 2026-03-05 14:13:26 +01:00
parent 69362e7a96
commit 34597b9909
2 changed files with 87 additions and 28 deletions

View file

@ -139,26 +139,12 @@ class GarageScreenState extends State<GarageScreen> {
Future<void> _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;

View file

@ -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 = <String, int>{};
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 = <String, int>{};
await Future.wait(collectionIdList.map((collectionId) async {
@ -171,6 +161,89 @@ class CollectionService {
return collections;
}
static Future<Map<String, int>> getCollectionItemCounts(
List<String> collectionIds) async {
if (collectionIds.isEmpty) return <String, int>{};
final itemCounts = <String, int>{};
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<String, dynamic>;
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<int> _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<Collection> create({
required String name,