aiQuota function
Fetch the current user's AI usage + plan caps.
Reads rate_limits for the rolling day/week counters (and all-time total
for trial users) and current_entitlements() for the per-plan caps.
Returns zeros when signed out so UI code can render without auth guards.
Implementation
@riverpod
Future<AiQuotaSnapshot> aiQuota(Ref ref) async {
// Invalidate when auth state changes.
ref.watch(authStateProvider);
// Re-fetch whenever the entitlement changes (e.g. plan upgrade, grant).
final ent = await ref.watch(currentEntitlementsProvider.future);
final client = Supabase.instance.client;
if (client.auth.currentUser == null) {
return const AiQuotaSnapshot(dayCount: 0, weekCount: 0);
}
try {
final now = DateTime.now();
final dayStart = DateTime(now.year, now.month, now.day);
final weekStart = now.subtract(const Duration(days: 7));
final hasTotalCap = ent.aiTotalCap != null;
// If the user has a total cap (trial), fetch ALL rate_limit rows to
// compute the all-time total. Otherwise, the 7-day window suffices.
final query = client
.from('rate_limits')
.select('window_start, request_count')
.eq('user_id', client.auth.currentUser!.id);
final rows = hasTotalCap
? await query
: await query.gte('window_start', weekStart.toIso8601String());
var dayCount = 0;
var weekCount = 0;
var totalCount = 0;
for (final row in rows as List<dynamic>) {
final map = row as Map<String, dynamic>;
final windowStart = DateTime.parse(map['window_start'] as String);
final count = map['request_count'] as int;
totalCount += count;
if (!windowStart.isBefore(weekStart)) weekCount += count;
if (!windowStart.isBefore(dayStart)) dayCount += count;
}
return AiQuotaSnapshot(
dailyCap: ent.aiDailyCap,
weeklyCap: ent.aiWeeklyCap,
totalCap: ent.aiTotalCap,
dayCount: dayCount,
weekCount: weekCount,
totalCount: totalCount,
);
} catch (e) {
dev.log('aiQuota fetch failed: $e', name: 'aiQuotaProvider');
return AiQuotaSnapshot(
dailyCap: ent.aiDailyCap,
weeklyCap: ent.aiWeeklyCap,
totalCap: ent.aiTotalCap,
dayCount: 0,
weekCount: 0,
);
}
}