Skip to content

release: Merging develop to Production release#247

Merged
Rajesh1041 merged 6 commits into
mainfrom
develop
Mar 26, 2026
Merged

release: Merging develop to Production release#247
Rajesh1041 merged 6 commits into
mainfrom
develop

Conversation

@Rajesh1041
Copy link
Copy Markdown
Collaborator

@Rajesh1041 Rajesh1041 commented Mar 26, 2026

Changes

  • Refer to MR-13 for list of changes

Ref: MR-45

Summary by CodeRabbit

  • Bug Fixes

    • Improved validation and error handling for event payload storage
    • Enhanced numeric data handling in merge operations
    • Strengthened type checking for data integrity
  • Chores

    • Optimized internal event data storage logic

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 26, 2026

📝 Walkthrough

Walkthrough

The file refactors payload storage by replacing a single method with branched logic based on collection type. user_session_data uses direct writes, while summary_data implements query-first upsert with data merging for existing documents and enhanced numeric merge handling.

Changes

Cohort / File(s) Summary
Payload Storage Refactor
app/src/main/java/org/curiouslearning/container/core/subapp/handler/DefaultAppEventPayloadHandler.java
Replaced storeSubAppPayload with storePayload branching logic. For user_session_data: direct .add() writes. For summary_data: query-first approach with merge logic that updates existing documents via ID or creates new ones. Enhanced type handling for merge operations, including distinct integral vs. non-integral numeric merging and permissive Map<?, ?> conversion for options. Added validation checks for payload.data type in both paths.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Update DefaultAppEventPayloadHandler.java #246: Implements the same refactoring of DefaultAppEventPayloadHandler.java, replacing storeSubAppPayload with storePayload and adding branched write logic for different collection types with query-first merge patterns.

Suggested reviewers

  • miguelccodev
  • dz4va

Poem

🐰 A branching path through data flows,
Where sessions hop and summaries grow,
Query first, then merge with care,
Numbers blend in pairs so fair!
Storage paths now split just right,

🚥 Pre-merge checks | ✅ 1 | ❌ 3

❌ Failed checks (1 warning, 2 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 ❓ Inconclusive Repository appears empty or files not properly cloned; cannot access DefaultAppEventPayloadHandler.java or test files to verify testing, linting, and coverage requirements. Ensure repository is properly cloned with all project files available, then verify test files exist, check build.gradle for test/lint/coverage configs, and confirm code changes have corresponding test coverage.
Title check ❓ Inconclusive The title 'release: Merging develop to Production release' is vague and generic, using non-descriptive branch management language that does not convey meaningful information about the actual code changes. Replace with a descriptive title that reflects the main technical changes, such as 'refactor: Refactor payload storage logic for summary and session data' or 'feat: Implement branched payload storage and improve merge handling for summary data'.
✅ 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
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch develop

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.

Actionable comments posted: 2

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

209-227: Keep options aligned with the payload contract.

AppEventPayload.options is already Map<String, String>, and AppEventPayloadValidator only allows "add" or "replace". Rebuilding it as Map<?, ?>Map<String, Object> weakens that contract and silently turns malformed values into "replace" instead of failing fast. Reading it as Map<String, String> keeps the handler consistent with the validator.

♻️ Suggested simplification
-        Map<String, Object> options = new HashMap<>();
-
-        if (payload.options instanceof Map) {
-            Map<?, ?> raw = (Map<?, ?>) payload.options;
-            for (Map.Entry<?, ?> entry : raw.entrySet()) {
-                options.put(String.valueOf(entry.getKey()), entry.getValue());
-            }
-        }
+        Map<String, String> options =
+                payload.options != null
+                        ? payload.options
+                        : java.util.Collections.emptyMap();
 ...
-            String operation =
-                    options.get(key) instanceof String
-                            ? (String) options.get(key)
-                            : "replace";
+            String operation = options.getOrDefault(key, "replace");
🤖 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 209 - 227, The handler rebuilds payload.options into
Map<String,Object>, weakening the contract; change the local options to
Map<String,String> and read it directly from payload.options (e.g.,
Map<String,String> options = payload.options != null ? payload.options :
Collections.emptyMap()), stop converting values to Object, and when computing
operation (the operation variable used alongside newData and merged in
DefaultAppEventPayloadHandler) retrieve the String via options.get(key) (with a
null check) so you preserve the payload contract and rely on
AppEventPayloadValidator's "add"/"replace" guarantees rather than silently
coercing malformed values.
🤖 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 106-118: In storeSummaryPayload, add a fast-fail guard that
validates payload.data is a Map (and non-null) before performing the Firestore
lookup; if payload.data is missing or not an instance of Map, log a
warning/error and return immediately to avoid calling query.get() or writing
back an unchanged record (this prevents mergeData from being invoked on
malformed data). Locate the validation at the start of storeSummaryPayload (use
AppEventPayload.data and the mergeData call site as references) and ensure no DB
reads/writes occur when the guard rejects the payload.
- Around line 113-161: storeSummaryPayload performs a non-atomic query-get +
client-side merge (mergeData) followed by set/create (createNewSummaryDoc),
which is TOCTOU-prone and also treats query failures as "no record" causing
duplicates; fix by computing a deterministic document ID (e.g., derive from
payload.cr_user_id + payload.app_id + payload.collection) and then perform an
atomic write (use Firestore transaction or document().set with merge on that
deterministic ID) instead of query-first logic, move payload.data validation up
front to match storeUserSessionPayload before any database ops, and change the
error branch to fail/retry the operation rather than assume missing record.
Ensure storeSummaryPayload, mergeData, and createNewSummaryDoc are updated to
use the deterministic ID or transaction-based update.

---

Nitpick comments:
In
`@app/src/main/java/org/curiouslearning/container/core/subapp/handler/DefaultAppEventPayloadHandler.java`:
- Around line 209-227: The handler rebuilds payload.options into
Map<String,Object>, weakening the contract; change the local options to
Map<String,String> and read it directly from payload.options (e.g.,
Map<String,String> options = payload.options != null ? payload.options :
Collections.emptyMap()), stop converting values to Object, and when computing
operation (the operation variable used alongside newData and merged in
DefaultAppEventPayloadHandler) retrieve the String via options.get(key) (with a
null check) so you preserve the payload contract and rely on
AppEventPayloadValidator's "add"/"replace" guarantees rather than silently
coercing malformed values.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 567a7169-bc36-499b-afde-2b09cf4c3755

📥 Commits

Reviewing files that changed from the base of the PR and between 6f93840 and 6c4db71.

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

Comment on lines +106 to +118
private void storeSummaryPayload(
FirebaseFirestore db,
AppEventPayload payload
) {

Log.d(TAG, "Querying summary record (offline-first)");

Query query = db.collection(payload.collection)
.whereEqualTo("cr_user_id", payload.cr_user_id)
.whereEqualTo("app_id", payload.app_id)
.limit(1);

query.get()
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 | 🟠 Major

Reject malformed summary_data before the lookup.

This path does not fail fast on non-Map payload.data. If a summary already exists, mergeData() logs the error but returns the previous map, and this method still writes the record back with a new timestamp. Invalid summary events should return before any read/write.

🛡️ Suggested guard
 private void storeSummaryPayload(
         FirebaseFirestore db,
         AppEventPayload payload
 ) {
+        if (!(payload.data instanceof Map)) {
+            Log.e(TAG, "Invalid payload.data type. Expected Map but got: "
+                    + (payload.data == null ? "null" : payload.data.getClass()));
+            return;
+        }
+
         Log.d(TAG, "Querying summary record (offline-first)");
🤖 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 106 - 118, In storeSummaryPayload, add a fast-fail guard that
validates payload.data is a Map (and non-null) before performing the Firestore
lookup; if payload.data is missing or not an instance of Map, log a
warning/error and return immediately to avoid calling query.get() or writing
back an unchanged record (this prevents mergeData from being invoked on
malformed data). Locate the validation at the start of storeSummaryPayload (use
AppEventPayload.data and the mergeData call site as references) and ensure no DB
reads/writes occur when the guard rejects the payload.

Comment on lines +113 to +161
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()) {

List<DocumentSnapshot> documents = querySnapshot.getDocuments();
DocumentSnapshot existingDoc = documents.get(0);

Map<String, Object> mergedData =
mergeData(existingDoc, payload);

record.put("data", mergedData);
record.put("data", mergedData);

// ✅ 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));
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);
}
})
.addOnFailureListener(e -> {

Log.w(TAG, "Query failed — creating new summary record", e);

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

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

🧩 Analysis chain

🏁 Script executed:

fd -t f "DefaultAppEventPayloadHandler.java" --exec wc -l {}

Repository: curiouslearning/CRcontainer

Length of output: 179


🏁 Script executed:

fd -t f "DefaultAppEventPayloadHandler.java" --exec cat -n {}

Repository: curiouslearning/CRcontainer

Length of output: 10989


🏁 Script executed:

fd -t f "AppEventPayload.java" --exec wc -l {}

Repository: curiouslearning/CRcontainer

Length of output: 164


🏁 Script executed:

fd -t f "AppEventPayload.java" --exec cat -n {}

Repository: curiouslearning/CRcontainer

Length of output: 457


🏁 Script executed:

fd -t f "AppEventPayloadValidator.java" --exec cat -n {}

Repository: curiouslearning/CRcontainer

Length of output: 1972


🏁 Script executed:

# Verify there are no other query-based upsert patterns in the codebase that might mitigate this
rg "query\.get\(\)" --type java -A 5

Repository: curiouslearning/CRcontainer

Length of output: 956


Make the summary_data upsert atomic.

query.get() + client-side merge + separate set()/add() is a TOCTOU write. Concurrent events for the same cr_user_id/app_id can both read the same snapshot, merge independently, and then both write—causing the second to overwrite the first's merged changes. The query-failure branch (line 151) also incorrectly treats transient read errors as absence, creating duplicate documents. Use a deterministic document ID or a transactional update path instead of query-first upsert.

Additionally, storeSummaryPayload lacks upfront validation of payload.data before the query, unlike storeUserSessionPayload (lines 77–81). This allows malformed payloads to reach the merge logic.

🤖 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 - 161, storeSummaryPayload performs a non-atomic query-get +
client-side merge (mergeData) followed by set/create (createNewSummaryDoc),
which is TOCTOU-prone and also treats query failures as "no record" causing
duplicates; fix by computing a deterministic document ID (e.g., derive from
payload.cr_user_id + payload.app_id + payload.collection) and then perform an
atomic write (use Firestore transaction or document().set with merge on that
deterministic ID) instead of query-first logic, move payload.data validation up
front to match storeUserSessionPayload before any database ops, and change the
error branch to fail/retry the operation rather than assume missing record.
Ensure storeSummaryPayload, mergeData, and createNewSummaryDoc are updated to
use the deterministic ID or transaction-based update.

@Rajesh1041 Rajesh1041 requested a review from miguelccodev March 26, 2026 06:23
@miguelccodev miguelccodev changed the title Merging develop to Production release release: Merging develop to Production release Mar 26, 2026
@Rajesh1041 Rajesh1041 merged commit 595761c into main Mar 26, 2026
1 of 3 checks 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