Skip to content

fix: guard rating calculation when ratingCount is zero (fixes #780)#781

Closed
dolliecoder wants to merge 2 commits intoAOSSIE-Org:masterfrom
dolliecoder:fix/guard-rating-division
Closed

fix: guard rating calculation when ratingCount is zero (fixes #780)#781
dolliecoder wants to merge 2 commits intoAOSSIE-Org:masterfrom
dolliecoder:fix/guard-rating-division

Conversation

@dolliecoder
Copy link

@dolliecoder dolliecoder commented Mar 2, 2026

Description

Fixes #780
Guards the userRating calculation in explore_story_controller.dart to prevent
invalid values (NaN/Infinity) when ratingCount is zero or null.

Previously, ratingTotal / ratingCount could produce invalid numeric results.
The calculation now safely returns 0.0 when ratingCount is not greater than zero.


Type of change

  • Bug fix (non-breaking change which fixes an issue)

How Has This Been Tested?

  • Verified rating calculation for:
    • Users with ratings (normal division works as expected)
    • Users with zero ratings (returns 0.0 instead of NaN/Infinity)

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • My changes generate no new warnings

Summary by CodeRabbit

  • Bug Fixes

    • Fixed user rating calculation to avoid division errors when ratings are missing.
    • Prevented attempts to remove non-existent likes to avoid runtime errors.
    • Added guards in realtime handlers to skip processing when no events are present, reducing crashes and spurious updates.
  • Chores

    • Strengthened error handling when expected data lists are empty.

@dolliecoder dolliecoder requested a review from M4dhav as a code owner March 2, 2026 11:30
@github-actions
Copy link
Contributor

github-actions bot commented Mar 2, 2026

🎉 Welcome @dolliecoder!
Thank you for your pull request! Our team will review it soon. 🔍

  • Please ensure your PR follows the contribution guidelines. ✅
  • All automated tests should pass before merging. 🔄
  • If this PR fixes an issue, link it in the description. 🔗

We appreciate your contribution! 🚀

@coderabbitai
Copy link

coderabbitai bot commented Mar 2, 2026

📝 Walkthrough

Walkthrough

Added safety guards and tightened control flow across several controllers: rating division now checks for zero counts, realtime listeners short-circuit when events are empty, like deletion only occurs when a like document exists, a participant lookup can return null, and a subscriber removal now throws if no documents found.

Changes

Cohort / File(s) Summary
Explore Story — rating & unlike fixes
lib/controllers/explore_story_controller.dart
Compute userRating using ratingCount > 0 ? ratingTotal / ratingCount : 0.0 in both Appwrite and Meilisearch conversion paths. In unlikeStoryFromUserAccount, delete like document only when userLikeDocuments is non-empty; otherwise log absence.
Realtime listeners — early-return guards
lib/controllers/friend_calling_controller.dart, lib/controllers/pair_chat_controller.dart, lib/controllers/room_chat_controller.dart, lib/controllers/live_chapter_controller.dart
Added early-return guards in realtime subscription handlers to exit immediately when data.events is empty, preventing processing of empty payloads.
Single room — nullable participant lookup & guard
lib/controllers/single_room_controller.dart
Added early-return when realtime data.events is empty; getParticipantDocId(Participant) now returns null when no matching participant document is found (introduces nullable return path).
Upcoming rooms — strict document handling
lib/controllers/upcomming_rooms_controller.dart
In removeUserFromSubscriberList, throw Exception('No documents found') if value.documents is empty; otherwise return value.documents.first, changing error handling and introducing a runtime error path.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hopped through code with careful feet,
Guarding divisions so errors meet defeat,
I skip empty events, and check each like,
Return null when no participant's in sight,
A tidy patch — a rabbit's neat delight 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title focuses on the rating calculation guard in explore_story_controller.dart, but the PR makes substantial changes to six controller files with multiple unrelated guards added to realtime listeners and collection access patterns. Update the title to reflect the broader scope: something like 'fix: add defensive guards for null/empty collections in controllers' or provide a more comprehensive description of all changes made.
✅ Passed checks (2 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/controllers/single_room_controller.dart`:
- Around line 248-250: Change getParticipantDocId's signature from
Future<String> to Future<String?> and have it return null when
participantDocsRef.documents.isEmpty; then update every callsite (makeModerator,
removeModerator, makeSpeaker, makeListener, kickOutParticipant) to await
getParticipantDocId into a nullable variable, check for null and early-return if
null (e.g. final participantDocId = await getParticipantDocId(...); if
(participantDocId == null) return;), and then call
updateParticipantDoc(participantDocId, ...) only when non-null. Ensure all
signatures and usages reflect the nullable return type.

In `@lib/controllers/upcomming_rooms_controller.dart`:
- Around line 119-124: Replace the throw in the query result handler so empty
results are treated as a no-op instead of throwing a generic uncaught Exception:
in the .then((value) { ... }) block that currently checks
value.documents.isEmpty and throws Exception('No documents found'), remove the
throw and return a sensible empty sentinel (e.g. null or an empty optional/empty
result) so callers can handle "no record" normally; update any downstream
handling of the returned value from this query chain to treat the sentinel as an
early return/unsubscribed case instead of relying on an exception.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4013959 and dd601ec.

📒 Files selected for processing (7)
  • lib/controllers/explore_story_controller.dart
  • lib/controllers/friend_calling_controller.dart
  • lib/controllers/live_chapter_controller.dart
  • lib/controllers/pair_chat_controller.dart
  • lib/controllers/room_chat_controller.dart
  • lib/controllers/single_room_controller.dart
  • lib/controllers/upcomming_rooms_controller.dart
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/controllers/explore_story_controller.dart

Comment on lines +248 to +250
if (participantDocsRef.documents.isEmpty) {
return null; // or handle appropriately
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's locate and read the file to understand the current state
git ls-files lib/controllers/single_room_controller.dart

Repository: AOSSIE-Org/Resonate

Length of output: 106


🏁 Script executed:

# Get the file size to understand how to read it
wc -l lib/controllers/single_room_controller.dart

Repository: AOSSIE-Org/Resonate

Length of output: 110


🏁 Script executed:

# Search for the getParticipantDocId method declaration and implementation
rg -n "getParticipantDocId" lib/controllers/single_room_controller.dart -A 20

Repository: AOSSIE-Org/Resonate

Length of output: 3190


🏁 Script executed:

# Search for where the method returns null or is called
rg -n "return null" lib/controllers/single_room_controller.dart -B 3 -A 1

Repository: AOSSIE-Org/Resonate

Length of output: 198


🏁 Script executed:

# Check all callsites of getParticipantDocId to see how they handle the result
rg -n "await getParticipantDocId\|getParticipantDocId(" lib/controllers/single_room_controller.dart -B 2 -A 3

Repository: AOSSIE-Org/Resonate

Length of output: 207


🏁 Script executed:

# Check for analysis configuration
fd -name "analysis_options.yaml" -o -name "pubspec.yaml" | head -5 | xargs grep -l "enable-null-safety\|strict-null-safety" 2>/dev/null || echo "Checking analysis_options.yaml..."

Repository: AOSSIE-Org/Resonate

Length of output: 344


🏁 Script executed:

# Check the analysis configuration file
cat analysis_options.yaml 2>/dev/null | head -30 || echo "No analysis_options.yaml found"

Repository: AOSSIE-Org/Resonate

Length of output: 1470


🏁 Script executed:

# Check pubspec.yaml for null-safety configuration
cat pubspec.yaml 2>/dev/null | grep -A 5 "environment\|null-safety" || echo "No environment config found"

Repository: AOSSIE-Org/Resonate

Length of output: 328


Null-safety violation: non-nullable method returns null.

getParticipantDocId is declared Future<String> but returns null at line 249, which breaks Dart's null-safety constraints and will fail static analysis. Make the return type Future<String?> and add null checks at all callsites (makeModerator, removeModerator, makeSpeaker, makeListener, kickOutParticipant):

final participantDocId = await getParticipantDocId(participant);
if (participantDocId == null) return;
await updateParticipantDoc(participantDocId, {...});

Also update the method signature to Future<String?>.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/controllers/single_room_controller.dart` around lines 248 - 250, Change
getParticipantDocId's signature from Future<String> to Future<String?> and have
it return null when participantDocsRef.documents.isEmpty; then update every
callsite (makeModerator, removeModerator, makeSpeaker, makeListener,
kickOutParticipant) to await getParticipantDocId into a nullable variable, check
for null and early-return if null (e.g. final participantDocId = await
getParticipantDocId(...); if (participantDocId == null) return;), and then call
updateParticipantDoc(participantDocId, ...) only when non-null. Ensure all
signatures and usages reflect the nullable return type.

Comment on lines +119 to +124
.then((value) {
if (value.documents.isEmpty) {
throw Exception('No documents found');
}
return value.documents.first;
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid throwing an uncaught generic exception on empty query results.

This new Exception('No documents found') is not caught by the current on AppwriteException block, so normal “already unsubscribed / no record” cases can fail unexpectedly. Prefer a no-op early return.

💡 Suggested fix
-      var subscribeDocument = await databases
+      final docs = await databases
           .listDocuments(
             databaseId: upcomingRoomsDatabaseId,
             collectionId: subscribedUserCollectionId,
             queries: [
               Query.and([
                 Query.equal('userID', authStateController.uid),
                 Query.equal('upcomingRoomId', upcomingRoomId),
               ]),
             ],
           )
-          .then((value) {
-            if (value.documents.isEmpty) {
-              throw Exception('No documents found');
-            }
-            return value.documents.first;
-          });
+          .then((value) => value.documents);
+
+      if (docs.isEmpty) {
+        return;
+      }
+      final subscribeDocument = docs.first;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/controllers/upcomming_rooms_controller.dart` around lines 119 - 124,
Replace the throw in the query result handler so empty results are treated as a
no-op instead of throwing a generic uncaught Exception: in the .then((value) {
... }) block that currently checks value.documents.isEmpty and throws
Exception('No documents found'), remove the throw and return a sensible empty
sentinel (e.g. null or an empty optional/empty result) so callers can handle "no
record" normally; update any downstream handling of the returned value from this
query chain to treat the sentinel as an early return/unsubscribed case instead
of relying on an exception.

@dolliecoder dolliecoder closed this Mar 2, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Mar 2, 2026

PR Closed - Thank You, @dolliecoder!

  • If this PR was merged: Congratulations! Your contribution is now part of the project. 🚀
  • If this PR was closed without merging: Don’t worry! You can always improve it and submit again. 💪

We appreciate your effort and look forward to more contributions from you! 🤝

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Potential invalid userRating calculation when ratingCount is zero

1 participant