aiQuota function

  1. @riverpod
Future<AiQuotaSnapshot> aiQuota(
  1. Ref<Object?> ref
)

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,
    );
  }
}