Skip to content

Update DefaultAppEventPayloadHandler.java#246

Merged
Rajesh1041 merged 5 commits into
developfrom
MR-57
Mar 9, 2026
Merged

Update DefaultAppEventPayloadHandler.java#246
Rajesh1041 merged 5 commits into
developfrom
MR-57

Conversation

@Rajesh1041
Copy link
Copy Markdown
Collaborator

@Rajesh1041 Rajesh1041 commented Mar 7, 2026

Changes

  • app/src/main/java/org/curiouslearning/container/core/subapp/handler/DefaultAppEventPayloadHandler.java

Ref: MR-57

Summary by CodeRabbit

  • New Features
    • Routes event storage by collection: dedicated paths for user session data and summary data with explicit create-or-update behavior.
  • Bug Fixes
    • Stronger validation of event payloads to prevent invalid stores.
    • Improved summary merging and numeric "add" handling with safer type checks.
  • Refactor
    • Reorganized storage flow and logging for clearer, more reliable event processing.

@Rajesh1041 Rajesh1041 requested a review from miguelccodev March 7, 2026 13:03
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 7, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Renamed internal handler to storePayload; added collection-based routing that writes user_session_data as session docs and handles summary_data by querying for existing summary, merging or creating a summary document, with stricter payload validation and expanded logging.

Changes

Cohort / File(s) Summary
Payload handling & Firestore routing
app/src/main/java/org/curiouslearning/container/core/subapp/handler/DefaultAppEventPayloadHandler.java
Renamed storeSubAppPayloadstorePayload; added routing on payload.collection to storeUserSessionPayload (direct add) and storeSummaryPayload (query by cr_user_id+app_id, merge via mergeData(...) and update, or createNewSummaryDoc(...) on no-result/error). Added validation of payload fields, options handling (default "replace"), numeric "add" support, extra logs, replaced upsert with explicit create/update, and added imports (List, Query).

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Handler as DefaultAppEventPayloadHandler
  participant Firestore

  Client->>Handler: send AppEventPayload
  Handler->>Handler: validate cr_user_id, app_id, collection, timestamp
  alt invalid
    Handler->>Handler: log error, abort
  else valid
    Handler->>Handler: inspect collection
    alt user_session_data
      Handler->>Firestore: add session document (cr_user_id, app_id, collection, timestamp, data)
      Firestore-->>Handler: success / failure
      Handler->>Handler: log result
    else summary_data
      Handler->>Firestore: query doc by cr_user_id & app_id (limit 1)
      alt document found
        Firestore-->>Handler: existing doc
        Handler->>Handler: mergeData(existing.data, payload.data, options)
        Handler->>Firestore: update document with merged data
        Firestore-->>Handler: success / failure
        Handler->>Handler: log result
      else no document / query error
        Firestore-->>Handler: no result / error
        Handler->>Firestore: create new summary document
        Firestore-->>Handler: success / failure
        Handler->>Handler: log result
      end
    end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • miguelccodev

Poem

🐇 I hopped through payloads, checked each id and time,
Sessions I planted, summaries I prime.
I merge where I can, or craft a new page,
Logging my hops as I tidy the stage. ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Tests & Lint & Coverage ⚠️ Warning Pull request adds 263 lines of complex code to DefaultAppEventPayloadHandler with zero test coverage and no automated tests created or updated. Create comprehensive unit tests for DefaultAppEventPayloadHandler covering all code paths and verify 70% test coverage using jacocoTestReport task.
Title check ❓ Inconclusive The title is vague and overly generic. It uses 'Update' without specifying what was changed, failing to convey the substantive refactoring of payload routing logic and handler methods. Revise the title to be more descriptive, such as: 'Refactor DefaultAppEventPayloadHandler payload routing and add collection-based dispatching' to clearly communicate the main changes.
✅ Passed checks (1 passed)
Check name Status Explanation
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 docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch MR-57

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
Copy Markdown

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/src/main/java/org/curiouslearning/container/core/subapp/handler/DefaultAppEventPayloadHandler.java (1)

36-44: ⚠️ Potential issue | 🟡 Minor

Inconsistent validation for timestamp field.

The validation checks trim().isEmpty() for cr_user_id, app_id, and collection, but only checks != null for timestamp. If blank timestamps should also be rejected, add the same trim check for consistency.

🔧 Proposed fix for consistent validation
         if (payload.cr_user_id == null || payload.cr_user_id.trim().isEmpty() ||
                 payload.app_id == null || payload.app_id.trim().isEmpty() ||
                 payload.collection == null || payload.collection.trim().isEmpty() ||
-                payload.timestamp == null) {
+                payload.timestamp == null || payload.timestamp.trim().isEmpty()) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/org/curiouslearning/container/core/subapp/handler/DefaultAppEventPayloadHandler.java`
around lines 36 - 44, The payload validation in DefaultAppEventPayloadHandler
currently checks payload.cr_user_id, payload.app_id, and payload.collection for
null or blank but only checks payload.timestamp for null; update the validation
to also reject blank/empty timestamps by adding the same trim().isEmpty() check
for payload.timestamp (i.e., ensure payload.timestamp != null &&
!payload.timestamp.trim().isEmpty()) alongside the other field checks and keep
the same Log.e(TAG, ...) return behavior if the validation fails.
🧹 Nitpick comments (1)
app/src/main/java/org/curiouslearning/container/core/subapp/handler/DefaultAppEventPayloadHandler.java (1)

55-58: Unchecked cast of payload.data to Map<String, Object>.

The cast (Map<String, Object>) payload.data is unchecked and could fail at runtime if the Map has incompatible generic types. Consider adding a @SuppressWarnings("unchecked") annotation with a comment explaining why it's safe, or perform more defensive type checking.

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

In
`@app/src/main/java/org/curiouslearning/container/core/subapp/handler/DefaultAppEventPayloadHandler.java`
around lines 55 - 58, The unchecked cast of payload.data to Map<String, Object>
in DefaultAppEventPayloadHandler is unsafe; replace the direct cast with
defensive evidence: if payload.data instanceof Map<?,?> then iterate the entries
and copy only those with String keys into a new HashMap<String,Object> (checking
entry.getKey() instanceof String), otherwise create an empty map; alternatively,
if you opt to keep the cast, add a targeted `@SuppressWarnings`("unchecked") on
the smallest scope (e.g., a local helper method) with a comment explaining why
the payload contract guarantees a Map<String,Object> to silence the warning.
Ensure you reference the local variable name data and payload.data when making
the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In
`@app/src/main/java/org/curiouslearning/container/core/subapp/handler/DefaultAppEventPayloadHandler.java`:
- Around line 36-44: The payload validation in DefaultAppEventPayloadHandler
currently checks payload.cr_user_id, payload.app_id, and payload.collection for
null or blank but only checks payload.timestamp for null; update the validation
to also reject blank/empty timestamps by adding the same trim().isEmpty() check
for payload.timestamp (i.e., ensure payload.timestamp != null &&
!payload.timestamp.trim().isEmpty()) alongside the other field checks and keep
the same Log.e(TAG, ...) return behavior if the validation fails.

---

Nitpick comments:
In
`@app/src/main/java/org/curiouslearning/container/core/subapp/handler/DefaultAppEventPayloadHandler.java`:
- Around line 55-58: The unchecked cast of payload.data to Map<String, Object>
in DefaultAppEventPayloadHandler is unsafe; replace the direct cast with
defensive evidence: if payload.data instanceof Map<?,?> then iterate the entries
and copy only those with String keys into a new HashMap<String,Object> (checking
entry.getKey() instanceof String), otherwise create an empty map; alternatively,
if you opt to keep the cast, add a targeted `@SuppressWarnings`("unchecked") on
the smallest scope (e.g., a local helper method) with a comment explaining why
the payload contract guarantees a Map<String,Object> to silence the warning.
Ensure you reference the local variable name data and payload.data when making
the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b98134f0-b018-4315-afeb-6e73f62efee3

📥 Commits

Reviewing files that changed from the base of the PR and between 613b52d and a46d273.

📒 Files selected for processing (1)
  • app/src/main/java/org/curiouslearning/container/core/subapp/handler/DefaultAppEventPayloadHandler.java

Copy link
Copy Markdown

@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: 1

🧹 Nitpick comments (2)
app/src/main/java/org/curiouslearning/container/core/subapp/handler/DefaultAppEventPayloadHandler.java (2)

78-81: Unchecked cast warning.

The cast (Map<String, Object>) payload.data produces an unchecked cast warning since payload.data is typed as Object. This pattern repeats in createNewSummaryDoc and mergeData. Consider adding a @SuppressWarnings("unchecked") annotation to acknowledge this is intentional, or extract a helper method for safer conversion.

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

In
`@app/src/main/java/org/curiouslearning/container/core/subapp/handler/DefaultAppEventPayloadHandler.java`
around lines 78 - 81, The unchecked cast from payload.data to Map<String,
Object> in DefaultAppEventPayloadHandler (used in the shown block and also in
createNewSummaryDoc and mergeData) should be made explicit: either add
`@SuppressWarnings`("unchecked") to the containing method(s) (or the class) around
the cast sites, or extract a small helper like safeToMap(Object data) that
checks instanceof Map and returns a new HashMap<>((Map<String,Object>) data)
after annotating that helper with `@SuppressWarnings`("unchecked"); update
createNewSummaryDoc and mergeData to call that helper so the unchecked cast is
centralized and the compiler warning is resolved.

55-58: Log message is misleading for non-summary collections.

The log says "Handling summary_data payload" but this branch handles all collections that aren't "user_session_data", not just "summary_data". Consider making the log message more accurate.

Suggested fix
         } else {
 
-            Log.d(TAG, "Handling summary_data payload");
+            Log.d(TAG, "Handling payload for collection=" + payload.collection);
             storeSummaryPayload(db, payload);
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/org/curiouslearning/container/core/subapp/handler/DefaultAppEventPayloadHandler.java`
around lines 55 - 58, The log message in DefaultAppEventPayloadHandler's else
branch incorrectly states "Handling summary_data payload" even though it handles
any collection other than "user_session_data"; update the Log.d(TAG, ...) call
to use a generic or dynamic message (e.g., include the actual payload collection
name or say "Handling non-user_session_data payload") so it accurately reflects
the branch, and ensure storeSummaryPayload(db, payload) remains invoked as
before.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@app/src/main/java/org/curiouslearning/container/core/subapp/handler/DefaultAppEventPayloadHandler.java`:
- Around line 142-153: The addOnFailureListener block in
DefaultAppEventPayloadHandler currently creates a new summary document on any
query failure, which can produce duplicates; modify the failure handling in the
query's addOnFailureListener so it does not unconditionally call
createNewSummaryDoc(db, payload, record). Instead, generate a deterministic
document ID (for example derived from payload.cr_user_id + payload.app_id +
payload.collection) or perform a safe existence check (a get() by that
deterministic ID or a transactional read) before calling createNewSummaryDoc,
and/or implement a retry with exponential backoff for transient errors; update
the logic around the query failure handler and the createNewSummaryDoc
invocation to use that deterministic-id or guarded create to prevent duplicates.

---

Nitpick comments:
In
`@app/src/main/java/org/curiouslearning/container/core/subapp/handler/DefaultAppEventPayloadHandler.java`:
- Around line 78-81: The unchecked cast from payload.data to Map<String, Object>
in DefaultAppEventPayloadHandler (used in the shown block and also in
createNewSummaryDoc and mergeData) should be made explicit: either add
`@SuppressWarnings`("unchecked") to the containing method(s) (or the class) around
the cast sites, or extract a small helper like safeToMap(Object data) that
checks instanceof Map and returns a new HashMap<>((Map<String,Object>) data)
after annotating that helper with `@SuppressWarnings`("unchecked"); update
createNewSummaryDoc and mergeData to call that helper so the unchecked cast is
centralized and the compiler warning is resolved.
- Around line 55-58: The log message in DefaultAppEventPayloadHandler's else
branch incorrectly states "Handling summary_data payload" even though it handles
any collection other than "user_session_data"; update the Log.d(TAG, ...) call
to use a generic or dynamic message (e.g., include the actual payload collection
name or say "Handling non-user_session_data payload") so it accurately reflects
the branch, and ensure storeSummaryPayload(db, payload) remains invoked as
before.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7392c3e7-1425-499b-8c16-3cc98a82ddfe

📥 Commits

Reviewing files that changed from the base of the PR and between a46d273 and 7b37abb.

📒 Files selected for processing (1)
  • app/src/main/java/org/curiouslearning/container/core/subapp/handler/DefaultAppEventPayloadHandler.java

Updated merge logic to handle add/replace oprations
Copy link
Copy Markdown

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/src/main/java/org/curiouslearning/container/core/subapp/handler/DefaultAppEventPayloadHandler.java (1)

39-47: ⚠️ Potential issue | 🟠 Major

Reject unsupported data shapes before routing.

storeUserSessionPayload() turns non-Map payloads into {} on Lines 78-83, and mergeData() ignores non-Map updates on Lines 189-190. With the current guard, those events are still accepted and logged, but their body is silently dropped. Either validate payload.data instanceof Map here for collections that require object data, or preserve the raw payload.data in the user_session_data path instead of replacing it with an empty map.

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

In
`@app/src/main/java/org/curiouslearning/container/core/subapp/handler/DefaultAppEventPayloadHandler.java`
around lines 39 - 47, DefaultAppEventPayloadHandler currently accepts events
whose payload.data is not a Map but downstream storeUserSessionPayload() coerces
those to {} and mergeData() ignores non-Map updates, causing silent data loss
for user_session_data; update the validation in DefaultAppEventPayloadHandler to
explicitly reject events where payload.data is not a Map for collections that
require object data (or alternatively preserve the raw payload.data for the
user_session_data path), e.g., add a check referencing payload.data instanceof
Map and return/log an error when false for the user_session_data collection, or
modify storeUserSessionPayload() to preserve non-Map payload.data instead of
replacing it with an empty Map so mergeData() can handle it appropriately.
♻️ Duplicate comments (1)
app/src/main/java/org/curiouslearning/container/core/subapp/handler/DefaultAppEventPayloadHandler.java (1)

104-153: ⚠️ Potential issue | 🟠 Major

This query-then-create/update flow can still fork duplicate summary docs.

The read, empty-check, and write are separate operations. Two clients can both observe “no record” and both call createNewSummaryDoc(...), and the failure branch on Lines 142-152 also creates a new doc for any transient query error. After that, limit(1) only updates one copy. Use a deterministic document ID for the logical summary record, or move this to a Firestore transaction on a single document reference instead of query + add().

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

In
`@app/src/main/java/org/curiouslearning/container/core/subapp/handler/DefaultAppEventPayloadHandler.java`
around lines 104 - 153, The current query-then-create/update in
DefaultAppEventPayloadHandler can produce duplicate summary docs; replace the
query + conditional createNewSummaryDoc flow with a single-document
deterministic write or a Firestore transaction on a single document reference
(e.g., use a deterministic document ID derived from payload.cr_user_id +
payload.app_id + payload.collection) instead of calling
db.collection(payload.collection).add(...) in createNewSummaryDoc; if you prefer
transactions, use db.runTransaction(...) targeting
db.collection(payload.collection).document(deterministicId) and perform
read/mergeData(existingDoc, payload) and set(record) atomically, and ensure the
failure branch that currently calls createNewSummaryDoc also writes to the same
deterministic document reference or runs the same transaction so transient query
errors do not create duplicates.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@app/src/main/java/org/curiouslearning/container/core/subapp/handler/DefaultAppEventPayloadHandler.java`:
- Around line 215-223: The add branch in DefaultAppEventPayloadHandler currently
sums numbers via n1.doubleValue() + n2.doubleValue() which forces all numeric
fields to Double and can lose integer precision; change the logic in the "add"
case (the block that computes and does merged.put(key, ...)) to detect when both
operands are integral types (e.g., instances of Long/Integer/Short/Byte) and
perform the addition using longValue() and store a Long (or the widest integral
type present), otherwise fall back to doubleValue() and store a Double;
alternatively (and preferably for Firestore-backed state), replace the in-place
numeric addition with Firestore's FieldValue.increment(...) for atomic numeric
updates; update the code paths around merged.put(key, ...) in
DefaultAppEventPayloadHandler to preserve integral types or use
FieldValue.increment as appropriate.

---

Outside diff comments:
In
`@app/src/main/java/org/curiouslearning/container/core/subapp/handler/DefaultAppEventPayloadHandler.java`:
- Around line 39-47: DefaultAppEventPayloadHandler currently accepts events
whose payload.data is not a Map but downstream storeUserSessionPayload() coerces
those to {} and mergeData() ignores non-Map updates, causing silent data loss
for user_session_data; update the validation in DefaultAppEventPayloadHandler to
explicitly reject events where payload.data is not a Map for collections that
require object data (or alternatively preserve the raw payload.data for the
user_session_data path), e.g., add a check referencing payload.data instanceof
Map and return/log an error when false for the user_session_data collection, or
modify storeUserSessionPayload() to preserve non-Map payload.data instead of
replacing it with an empty Map so mergeData() can handle it appropriately.

---

Duplicate comments:
In
`@app/src/main/java/org/curiouslearning/container/core/subapp/handler/DefaultAppEventPayloadHandler.java`:
- Around line 104-153: The current query-then-create/update in
DefaultAppEventPayloadHandler can produce duplicate summary docs; replace the
query + conditional createNewSummaryDoc flow with a single-document
deterministic write or a Firestore transaction on a single document reference
(e.g., use a deterministic document ID derived from payload.cr_user_id +
payload.app_id + payload.collection) instead of calling
db.collection(payload.collection).add(...) in createNewSummaryDoc; if you prefer
transactions, use db.runTransaction(...) targeting
db.collection(payload.collection).document(deterministicId) and perform
read/mergeData(existingDoc, payload) and set(record) atomically, and ensure the
failure branch that currently calls createNewSummaryDoc also writes to the same
deterministic document reference or runs the same transaction so transient query
errors do not create duplicates.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0946b3d6-1642-47dc-b467-ff039275d2ac

📥 Commits

Reviewing files that changed from the base of the PR and between 7b37abb and 8c108b8.

📒 Files selected for processing (1)
  • app/src/main/java/org/curiouslearning/container/core/subapp/handler/DefaultAppEventPayloadHandler.java

Copy link
Copy Markdown

@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: 3

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

Inline comments:
In
`@app/src/main/java/org/curiouslearning/container/core/subapp/handler/DefaultAppEventPayloadHandler.java`:
- Around line 76-79: DefaultAppEventPayloadHandler currently discards non-Map
AppEventPayload.data by replacing it with an empty Map; update handling so
unsupported shapes are not silently dropped: either validate and reject non-Map
payloads with an explicit exception (e.g., throw IllegalArgumentException with
clear message) or persist payload.data as-is instead of normalizing to an empty
Map. Modify the code around the Map construction (the snippet using payload.data
instanceof Map) to check the runtime type of AppEventPayload.data and: if your
design requires only maps, validate and return an error; otherwise assign the
original payload.data to the record being stored (or wrap scalars/lists into a
field that preserves their shape). Apply the same change to the analogous blocks
at the other two sites referenced (around lines 160-163 and 187-191) so all
handlers consistently reject or preserve non-Map payloads.
- Around line 102-138: The current read-then-add flow in
DefaultAppEventPayloadHandler (the Query "query" and subsequent query.get()
branch that calls createNewSummaryDoc or
db.collection(...).document(existingDoc.getId()).set(...)) is racy; replace it
with a deterministic document ID (e.g., hash or concatenation of
payload.cr_user_id, payload.app_id, payload.collection) and perform an atomic
upsert against that document instead of querying+adding. Concretely: compute
docId from payload, use DocumentReference docRef =
db.collection(payload.collection).document(docId), then either run a Firestore
transaction that reads and merges via mergeData and writes the merged record, or
call docRef.set(record, SetOptions.merge())/docRef.set(record) as appropriate to
upsert atomically; apply the same change where the other summary-creation code
path (the other occurrence handling payload) is implemented so duplicate docs
cannot be created.
- Around line 48-57: The current handler in DefaultAppEventPayloadHandler treats
any non-empty payload.collection other than "user_session_data" as summary
storage; change this to an explicit allowlist: only accept "user_session_data"
and the exact summary collection name (e.g., "summary_data"), and if
payload.collection is anything else return/throw an error and do not call
storeUserSessionPayload or storeSummaryPayload. Update the conditional to check
equality against the allowed names (referencing payload.collection,
storeUserSessionPayload, storeSummaryPayload) and ensure the handler logs and
returns an error response for unsupported collection names so writes are
rejected rather than routed to an unintended Firestore collection.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0258adf7-66b0-4473-9669-c7989826bcf8

📥 Commits

Reviewing files that changed from the base of the PR and between 8c108b8 and 8cb3d97.

📒 Files selected for processing (1)
  • app/src/main/java/org/curiouslearning/container/core/subapp/handler/DefaultAppEventPayloadHandler.java

Comment on lines +102 to +138
Query query = db.collection(payload.collection)
.whereEqualTo("cr_user_id", payload.cr_user_id)
.whereEqualTo("app_id", payload.app_id)
.limit(1);

query.get()
.addOnSuccessListener(querySnapshot -> {

Map<String, Object> record = new HashMap<>();
record.put("cr_user_id", payload.cr_user_id);
record.put("app_id", payload.app_id);
record.put("collection", payload.collection);
record.put("timestamp", payload.timestamp);

Map<String, Object> mergedData =
mergeData(existingDoc, payload);
if (!querySnapshot.isEmpty()) {

record.put("data", mergedData);
List<DocumentSnapshot> documents = querySnapshot.getDocuments();
DocumentSnapshot existingDoc = documents.get(0);

// ✅ Idempotent write
documentRef.set(record, SetOptions.merge())
.addOnSuccessListener(aVoid ->
Log.d(TAG, "Summary payload upserted in Firestore"))
.addOnFailureListener(e ->
Log.e(TAG, "Failed to upsert summary payload", e));
Map<String, Object> mergedData =
mergeData(existingDoc, payload);

record.put("data", mergedData);

db.collection(payload.collection)
.document(existingDoc.getId())
.set(record)
.addOnSuccessListener(aVoid ->
Log.d(TAG, "Updated summary payload"))
.addOnFailureListener(e ->
Log.e(TAG, "Failed to update summary payload", e));

} else {

Log.d(TAG, "No existing summary record — creating new");
createNewSummaryDoc(db, payload, record);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Summary creation is still racy.

This is still a non-atomic read-then-create flow: two writers for the same (cr_user_id, app_id, collection) can both observe an empty query result and both call add(record), creating duplicate summary docs even when the query itself succeeds. After that, limit(1) makes future merges nondeterministic. Please use a deterministic document ID for the summary record or move this into a transaction/upsert on a fixed document.

Also applies to: 167-168

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

In
`@app/src/main/java/org/curiouslearning/container/core/subapp/handler/DefaultAppEventPayloadHandler.java`
around lines 102 - 138, The current read-then-add flow in
DefaultAppEventPayloadHandler (the Query "query" and subsequent query.get()
branch that calls createNewSummaryDoc or
db.collection(...).document(existingDoc.getId()).set(...)) is racy; replace it
with a deterministic document ID (e.g., hash or concatenation of
payload.cr_user_id, payload.app_id, payload.collection) and perform an atomic
upsert against that document instead of querying+adding. Concretely: compute
docId from payload, use DocumentReference docRef =
db.collection(payload.collection).document(docId), then either run a Firestore
transaction that reads and merges via mergeData and writes the merged record, or
call docRef.set(record, SetOptions.merge())/docRef.set(record) as appropriate to
upsert atomically; apply the same change where the other summary-creation code
path (the other occurrence handling payload) is implemented so duplicate docs
cannot be created.

Copy link
Copy Markdown

@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.

♻️ Duplicate comments (2)
app/src/main/java/org/curiouslearning/container/core/subapp/handler/DefaultAppEventPayloadHandler.java (2)

77-80: ⚠️ Potential issue | 🟠 Major

Reject unsupported data shapes upstream instead of dropping them here.

AppEventPayloadValidator only guarantees data != null, so scalar/list payloads still reach these branches. They are then logged and discarded here, which means the event was accepted but its body was not stored. Either validate Map payloads for these collections before handle() is called, or persist payload.data as-is.

Also applies to: 171-175, 202-205

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

In
`@app/src/main/java/org/curiouslearning/container/core/subapp/handler/DefaultAppEventPayloadHandler.java`
around lines 77 - 80, The handler currently drops non-Map payload.data in
DefaultAppEventPayloadHandler.handle(): locate the checks that log and return
when payload.data isn't a Map (the branches at the first check and the similar
ones around lines handling collections) and either (A) move/extend validation
into AppEventPayloadValidator so it rejects scalar/list shapes before handle()
is invoked (ensure AppEventPayloadValidator enforces Map type for data and
update its validation method signatures), or (B) persist payload.data unchanged
instead of logging-and-returning inside DefaultAppEventPayloadHandler.handle()
(update the persistence path to accept Object data and remove the early
returns). Pick one approach and apply the same fix to the other identical
branches referenced (the checks at ~171-175 and ~202-205).

113-162: ⚠️ Potential issue | 🔴 Critical

Make summary_data writes atomic.

This is still a query → client merge → add/set flow. Two writers can both create a new summary document, and concurrent "add" updates on an existing document can also lose increments because both are computed from stale snapshots. Once duplicates exist, limit(1) makes future merges nondeterministic. Please switch this path to a deterministic document ID plus a Firestore transaction, or use FieldValue.increment() for additive fields.

Also applies to: 182-187, 229-257

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

In
`@app/src/main/java/org/curiouslearning/container/core/subapp/handler/DefaultAppEventPayloadHandler.java`
around lines 113 - 162, The current read-then-write flow around the query in
DefaultAppEventPayloadHandler (the block that builds Query query =
db.collection(payload.collection)... and uses mergeData and createNewSummaryDoc)
is racy; replace it with a Firestore transaction (or use a deterministic
document ID derived from payload.cr_user_id and payload.app_id instead of
query+limit) and perform the read/merge/write inside db.runTransaction so the
mergeData logic runs atomically; for any additive counters in mergeData use
FieldValue.increment() in the transaction update to avoid lost increments, and
update createNewSummaryDoc and the branches at the other noted places (lines
~182-187 and ~229-257) to use the same deterministic doc id + transaction/update
pattern rather than query/get → set/add.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In
`@app/src/main/java/org/curiouslearning/container/core/subapp/handler/DefaultAppEventPayloadHandler.java`:
- Around line 77-80: The handler currently drops non-Map payload.data in
DefaultAppEventPayloadHandler.handle(): locate the checks that log and return
when payload.data isn't a Map (the branches at the first check and the similar
ones around lines handling collections) and either (A) move/extend validation
into AppEventPayloadValidator so it rejects scalar/list shapes before handle()
is invoked (ensure AppEventPayloadValidator enforces Map type for data and
update its validation method signatures), or (B) persist payload.data unchanged
instead of logging-and-returning inside DefaultAppEventPayloadHandler.handle()
(update the persistence path to accept Object data and remove the early
returns). Pick one approach and apply the same fix to the other identical
branches referenced (the checks at ~171-175 and ~202-205).
- Around line 113-162: The current read-then-write flow around the query in
DefaultAppEventPayloadHandler (the block that builds Query query =
db.collection(payload.collection)... and uses mergeData and createNewSummaryDoc)
is racy; replace it with a Firestore transaction (or use a deterministic
document ID derived from payload.cr_user_id and payload.app_id instead of
query+limit) and perform the read/merge/write inside db.runTransaction so the
mergeData logic runs atomically; for any additive counters in mergeData use
FieldValue.increment() in the transaction update to avoid lost increments, and
update createNewSummaryDoc and the branches at the other noted places (lines
~182-187 and ~229-257) to use the same deterministic doc id + transaction/update
pattern rather than query/get → set/add.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c682cc41-140f-422b-8f55-2375e0e5fd1c

📥 Commits

Reviewing files that changed from the base of the PR and between 8cb3d97 and 8af7502.

📒 Files selected for processing (1)
  • app/src/main/java/org/curiouslearning/container/core/subapp/handler/DefaultAppEventPayloadHandler.java

@Rajesh1041 Rajesh1041 merged commit 6c4db71 into develop Mar 9, 2026
1 check passed
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.

2 participants