Skip to content

Backend/feat: Add Kernel.External.Moengage for S2S event tracking#1217

Open
witcher-shailesh wants to merge 1 commit intomainfrom
backend/feat/moengage-s2s-integration
Open

Backend/feat: Add Kernel.External.Moengage for S2S event tracking#1217
witcher-shailesh wants to merge 1 commit intomainfrom
backend/feat/moengage-s2s-integration

Conversation

@witcher-shailesh
Copy link
Copy Markdown
Contributor

@witcher-shailesh witcher-shailesh commented Apr 15, 2026

Summary

  • Adds Kernel.External.Moengage module following the existing service integration pattern (similar to SOS, Insurance, Settlement)
  • Includes MoengageService enum, MoengageCfg with encrypted API secret, Servant API client, and pushEvent flow for Moengage S2S Event API
  • Enables server-side event tracking via Moengage's REST API with Basic auth, replacing unreliable mobile-side tracking

Files Added

  • Kernel/External/Moengage.hs — Re-export module
  • Kernel/External/Moengage/Types.hsMoengageService enum
  • Kernel/External/Moengage/Interface.hs — Provider dispatch
  • Kernel/External/Moengage/Interface/Types.hsMoengageServiceConfig sum type
  • Kernel/External/Moengage/Moengage/Config.hsMoengageCfg with EncryptedField
  • Kernel/External/Moengage/Moengage/Types.hs — API request/response types
  • Kernel/External/Moengage/Moengage/API.hs — Servant API definition
  • Kernel/External/Moengage/Moengage/Flow.hspushEvent with Basic auth

Test plan

  • Verify cabal build mobility-core compiles successfully
  • Verify JSON serialization of MoengageServiceConfig roundtrips correctly
  • Integration test with Moengage S2S Event API using test credentials

🤖 Generated with Claude Code

Summary by CodeRabbit

New Features

  • Added MoEngage integration to support event tracking and customer engagement management.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 15, 2026

Warning

Rate limit exceeded

@witcher-shailesh has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 20 minutes and 13 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 20 minutes and 13 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1b3cc01d-1ace-4630-8967-4d755c8c0711

📥 Commits

Reviewing files that changed from the base of the PR and between a1f02bd and 07114d6.

📒 Files selected for processing (9)
  • lib/mobility-core/mobility-core.cabal
  • lib/mobility-core/src/Kernel/External/Moengage.hs
  • lib/mobility-core/src/Kernel/External/Moengage/Interface.hs
  • lib/mobility-core/src/Kernel/External/Moengage/Interface/Types.hs
  • lib/mobility-core/src/Kernel/External/Moengage/Moengage/API.hs
  • lib/mobility-core/src/Kernel/External/Moengage/Moengage/Config.hs
  • lib/mobility-core/src/Kernel/External/Moengage/Moengage/Flow.hs
  • lib/mobility-core/src/Kernel/External/Moengage/Moengage/Types.hs
  • lib/mobility-core/src/Kernel/External/Moengage/Types.hs

Walkthrough

Introduces a new MoEngage S2S API integration module (Kernel.External.Moengage) with layered architecture including service interface, API client implementation, configuration management, and request/response types. The integration provides a pushEvent function that handles event delivery via HTTP with Basic Auth authentication.

Changes

Cohort / File(s) Summary
Service Interface Layer
Kernel.External.Moengage.hs, Kernel.External.Moengage/Interface.hs, Kernel.External.Moengage/Interface/Types.hs
Top-level module re-exports and public pushEvent function; MoengageServiceConfig wraps implementation-specific config via MoengageConfig constructor with JSON serialization support.
MoEngage API Definition
Kernel.External.Moengage/Moengage/API.hs
Servant API type for POST /v1/event endpoint with path-captured and query app_id parameters, Authorization header, and JSON request/response bodies.
Configuration & Service Registry
Kernel.External.Moengage/Moengage/Config.hs, Kernel.External.Moengage/Types.hs
MoengageCfg record with baseUrl, appId, encrypted apiSecret, and enabled flag; MoengageService enum with Moengage constructor and availableMoengageServices registry.
Flow Implementation & Data Types
Kernel.External.Moengage/Moengage/Flow.hs, Kernel.External.Moengage/Moengage/Types.hs
Flow-layer pushEvent with secret decryption, Basic Auth header construction, and API invocation; request/response types (MoengageEventReq, MoengageAction, MoengageEventResp) with custom JSON field name handling.

Sequence Diagram

sequenceDiagram
    participant Caller
    participant Interface as Interface Layer
    participant Flow as Flow Layer
    participant API as MoEngage API
    
    Caller->>Interface: pushEvent(config, request)
    Interface->>Flow: pushEvent(MoengageCfg, MoengageEventReq)
    Flow->>Flow: Decrypt apiSecret
    Flow->>Flow: Build Basic Auth Header
    Flow->>API: POST /v1/event with auth
    API-->>Flow: MoengageEventResp or error
    alt Success
        Flow-->>Interface: MoengageEventResp
    else Failure
        Flow-->>Interface: InternalError wrapped in monad
    end
    Interface-->>Caller: m MoengageEventResp
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A fluffy tale of push events sent,
With crypto keys and Base64 bent,
To Moengage's door, our messages fly,
Through Haskell types stacked way up high! ✨📨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: adding a new Moengage S2S event tracking integration module to the Kernel.External layer.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch backend/feat/moengage-s2s-integration

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.

witcher-shailesh added a commit to nammayatri/nammayatri that referenced this pull request Apr 15, 2026
Move ride event tracking from mobile app to backend for reliability.
Integrates with Moengage S2S API via MerchantServiceConfig pattern.

Events tracked:
- ny_user_first_ride_completed (first ride detection)
- ny_rider_ride_completed (with ride count)
- driver_assigned (driver assignment to booking)
- ny_user_source_and_destination (user search with lat/lon)
- ny_user_request_quotes (estimate count)

Depends on: nammayatri/shared-kernel#1217

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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: 5

🤖 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/mobility-core/src/Kernel/External/Moengage.hs`:
- Around line 21-22: This module is missing the required custom Prelude import
under NoImplicitPrelude; add an explicit import of Kernel.Prelude (or
EulerHS.Prelude per project convention) to the top of Kernel.External.Moengage
(the module that imports Kernel.External.Moengage.Interface and
Kernel.External.Moengage.Types) so the file uses the repo's Prelude replacement
instead of the standard Prelude and complies with the coding guideline.

In `@lib/mobility-core/src/Kernel/External/Moengage/Moengage/API.hs`:
- Around line 25-31: The MoengageEventAPI type incorrectly includes a
MandatoryQueryParam "app_id" — remove the MandatoryQueryParam "app_id" Text from
the type so the API only uses the Capture "app_id" Text path parameter; update
the MoengageEventAPI declaration to omit the query param reference and ensure
any related imports/usages referencing MandatoryQueryParam for this endpoint are
adjusted accordingly.

In `@lib/mobility-core/src/Kernel/External/Moengage/Moengage/Config.hs`:
- Around line 21-27: The derived Show on MoengageCfg can leak the apiSecret;
remove Show from the deriving list for MoengageCfg and provide a safe Show
instance that redacts or omits apiSecret (e.g., show other fields and display
"<redacted>" or omit the apiSecret field) so that calling show on MoengageCfg
cannot expose secrets; reference the MoengageCfg type and its apiSecret field
and keep other derived instances (Eq, Generic, ToJSON, FromJSON) unchanged.

In `@lib/mobility-core/src/Kernel/External/Moengage/Moengage/Flow.hs`:
- Around line 44-49: The pushEvent function currently always calls the MoEngage
API; update pushEvent to respect cfg.enabled by checking it at the top and
short-circuiting when disabled (i.e., do not decrypt, build auth, or call
callAPI). If cfg.enabled is False, return the appropriate successful/no-op
result for pushEvent (or log and return success) instead of performing the
external call; otherwise proceed as now (keep existing logic using
moengageClient, buildBasicAuth, callAPI and fromEitherM). Ensure you reference
cfg.enabled and preserve existing error handling paths
(fromEitherM/InternalError) when enabled.
- Line 49: The current error mapping in Flow.hs uses fromEitherM (\err ->
InternalError $ "Failed to call Moengage Event API: " <> show err), exposing raw
ClientError details; change it to return a generic, non-sensitive InternalError
message (e.g. "Failed to call Moengage Event API") without using show on the
error, and if needed capture/redact specific safe metadata in a separate
audit/log path (never inline the full show err). Update the mapping that
produces InternalError (the lambda passed to fromEitherM) to omit sensitive
details and apply the same replacement pattern to other external integrations
(Wallet, Ticket, Tokenize, Insurance, Payment, Payout, Maps) where fromEitherM
or similar error-to-InternalError conversions use show on low-level errors.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0ad6f640-81e2-408a-a357-3a6a69bc5763

📥 Commits

Reviewing files that changed from the base of the PR and between 3ba132b and a1f02bd.

📒 Files selected for processing (8)
  • lib/mobility-core/src/Kernel/External/Moengage.hs
  • lib/mobility-core/src/Kernel/External/Moengage/Interface.hs
  • lib/mobility-core/src/Kernel/External/Moengage/Interface/Types.hs
  • lib/mobility-core/src/Kernel/External/Moengage/Moengage/API.hs
  • lib/mobility-core/src/Kernel/External/Moengage/Moengage/Config.hs
  • lib/mobility-core/src/Kernel/External/Moengage/Moengage/Flow.hs
  • lib/mobility-core/src/Kernel/External/Moengage/Moengage/Types.hs
  • lib/mobility-core/src/Kernel/External/Moengage/Types.hs

Comment on lines +21 to +22
import Kernel.External.Moengage.Interface
import Kernel.External.Moengage.Types
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 | 🟡 Minor

Add the required Prelude import for this module.

This module currently skips Kernel.Prelude/EulerHS.Prelude, which violates the repo rule for Haskell modules.

✅ Minimal fix
 import Kernel.External.Moengage.Interface
 import Kernel.External.Moengage.Types
+import Kernel.Prelude ()

As per coding guidelines, "All modules must import Kernel.Prelude or EulerHS.Prelude instead of the standard Prelude due to NoImplicitPrelude language pragma".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import Kernel.External.Moengage.Interface
import Kernel.External.Moengage.Types
import Kernel.External.Moengage.Interface
import Kernel.External.Moengage.Types
import Kernel.Prelude ()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/mobility-core/src/Kernel/External/Moengage.hs` around lines 21 - 22, This
module is missing the required custom Prelude import under NoImplicitPrelude;
add an explicit import of Kernel.Prelude (or EulerHS.Prelude per project
convention) to the top of Kernel.External.Moengage (the module that imports
Kernel.External.Moengage.Interface and Kernel.External.Moengage.Types) so the
file uses the repo's Prelude replacement instead of the standard Prelude and
complies with the coding guideline.

Comment thread lib/mobility-core/src/Kernel/External/Moengage/Moengage/API.hs
Comment on lines +21 to +27
data MoengageCfg = MoengageCfg
{ baseUrl :: BaseUrl,
appId :: Text,
apiSecret :: EncryptedField 'AsEncrypted Text,
enabled :: Bool
}
deriving (Show, Eq, Generic, ToJSON, FromJSON)
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

Avoid deriving Show for secret-bearing config types.

Line 24 contains apiSecret; derived Show can leak credential material into logs (even encrypted form should be treated as sensitive).

🔐 Safer change
 data MoengageCfg = MoengageCfg
   { baseUrl :: BaseUrl,
     appId :: Text,
     apiSecret :: EncryptedField 'AsEncrypted Text,
     enabled :: Bool
   }
-  deriving (Show, Eq, Generic, ToJSON, FromJSON)
+  deriving (Eq, Generic, ToJSON, FromJSON)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
data MoengageCfg = MoengageCfg
{ baseUrl :: BaseUrl,
appId :: Text,
apiSecret :: EncryptedField 'AsEncrypted Text,
enabled :: Bool
}
deriving (Show, Eq, Generic, ToJSON, FromJSON)
data MoengageCfg = MoengageCfg
{ baseUrl :: BaseUrl,
appId :: Text,
apiSecret :: EncryptedField 'AsEncrypted Text,
enabled :: Bool
}
deriving (Eq, Generic, ToJSON, FromJSON)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/mobility-core/src/Kernel/External/Moengage/Moengage/Config.hs` around
lines 21 - 27, The derived Show on MoengageCfg can leak the apiSecret; remove
Show from the deriving list for MoengageCfg and provide a safe Show instance
that redacts or omits apiSecret (e.g., show other fields and display
"<redacted>" or omit the apiSecret field) so that calling show on MoengageCfg
cannot expose secrets; reference the MoengageCfg type and its apiSecret field
and keep other derived instances (Eq, Generic, ToJSON, FromJSON) unchanged.

Comment on lines +44 to +49
pushEvent cfg req = do
apiSecret <- decrypt cfg.apiSecret
let authToken = buildBasicAuth cfg.appId apiSecret
moengageClient = client (Proxy :: Proxy MoengageEventAPI)
callAPI cfg.baseUrl (moengageClient cfg.appId cfg.appId (Just authToken) req) "moengageEvent" moengageEventAPI
>>= fromEitherM (\err -> InternalError $ "Failed to call Moengage Event API: " <> show err)
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

Respect cfg.enabled before sending events.

Line 48 executes the external call unconditionally; disabled integrations can still emit traffic.

🧩 Suggested guard
 pushEvent cfg req = do
+  when (not cfg.enabled) $
+    throwError $ InternalError "Moengage integration is disabled"
   apiSecret <- decrypt cfg.apiSecret
   let authToken = buildBasicAuth cfg.appId apiSecret
       moengageClient = client (Proxy :: Proxy MoengageEventAPI)
   callAPI cfg.baseUrl (moengageClient cfg.appId cfg.appId (Just authToken) req) "moengageEvent" moengageEventAPI
     >>= fromEitherM (\err -> InternalError $ "Failed to call Moengage Event API: " <> show err)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/mobility-core/src/Kernel/External/Moengage/Moengage/Flow.hs` around lines
44 - 49, The pushEvent function currently always calls the MoEngage API; update
pushEvent to respect cfg.enabled by checking it at the top and short-circuiting
when disabled (i.e., do not decrypt, build auth, or call callAPI). If
cfg.enabled is False, return the appropriate successful/no-op result for
pushEvent (or log and return success) instead of performing the external call;
otherwise proceed as now (keep existing logic using moengageClient,
buildBasicAuth, callAPI and fromEitherM). Ensure you reference cfg.enabled and
preserve existing error handling paths (fromEitherM/InternalError) when enabled.

let authToken = buildBasicAuth cfg.appId apiSecret
moengageClient = client (Proxy :: Proxy MoengageEventAPI)
callAPI cfg.baseUrl (moengageClient cfg.appId cfg.appId (Just authToken) req) "moengageEvent" moengageEventAPI
>>= fromEitherM (\err -> InternalError $ "Failed to call Moengage Event API: " <> show err)
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

🧩 Analysis chain

🌐 Web query:

In servant-client, does showonClientErrorinclude request headers (for exampleAuthorization)? Please share source/docs for the exact rendered fields.

💡 Result:

No, the show instance for ClientError does not include sensitive request headers like Authorization. Since servant-client-core 0.16 (servant 0.16), the FailureResponse constructor includes the failing RequestF (BaseUrl, ByteString), which has a Show instance. A PR merged in servant 0.17 (#1238) specifically redacts the Authorization header in Show and exceptions to prevent credential leaks. Other headers are shown normally, but Authorization is redacted (e.g., shown as "REDACTED").

Citations:


🏁 Script executed:

# Find dependency files to check the servant version
find . -type f \( -name "*.cabal" -o -name "stack.yaml" -o -name "cabal.project" -o -name "package.yaml" \) | head -20

Repository: nammayatri/shared-kernel

Length of output: 249


🏁 Script executed:

# Read the file around line 49 to see the full context
cat -n lib/mobility-core/src/Kernel/External/Moengage/Moengage/Flow.hs | sed -n '40,60p'

Repository: nammayatri/shared-kernel

Length of output: 777


🏁 Script executed:

# Check the servant version in the project dependencies
cat lib/mobility-core/package.yaml | grep -A 5 "^dependencies:" | head -20

Repository: nammayatri/shared-kernel

Length of output: 156


🏁 Script executed:

# Also check if there's a specific servant-client dependency
rg "servant-client|servant-core" lib/mobility-core/package.yaml

Repository: nammayatri/shared-kernel

Length of output: 110


🏁 Script executed:

# Read the full package.yaml to see version constraints
cat lib/mobility-core/package.yaml | grep -A 100 "^dependencies:" | grep -E "(servant|version)"

Repository: nammayatri/shared-kernel

Length of output: 305


🏁 Script executed:

# Also check cabal.project for any version pins
cat cabal.project

Repository: nammayatri/shared-kernel

Length of output: 97


🏁 Script executed:

# Check if there's a stack.yaml file with version info
cat stack.yaml 2>/dev/null | head -50

Repository: nammayatri/shared-kernel

Length of output: 50


🏁 Script executed:

# Check the cabal.project file for version constraints
cat cabal.project | grep -E "servant|constraints"

Repository: nammayatri/shared-kernel

Length of output: 50


🏁 Script executed:

# Look for version constraints in the cabal file directly
cat lib/mobility-core/mobility-core.cabal | grep -A 100 "^build-depends:" | grep -E "servant" | head -10

Repository: nammayatri/shared-kernel

Length of output: 50


🏁 Script executed:

# Look at the full package.yaml to see all dependencies with versions
cat lib/mobility-core/package.yaml | grep -E "^[a-z]|version:" | head -50

Repository: nammayatri/shared-kernel

Length of output: 443


🏁 Script executed:

# Check the actual cabal.project file more carefully
cat cabal.project

Repository: nammayatri/shared-kernel

Length of output: 97


🏁 Script executed:

# Search for version bounds in package.yaml more comprehensively
rg "servant.*[<>=]|^ghc-version" lib/mobility-core/package.yaml

Repository: nammayatri/shared-kernel

Length of output: 50


🏁 Script executed:

# Look for any constraint or freeze files
find . -type f \( -name "*.freeze" -o -name "*.lock" -o -name "cabal.freeze" \) | head -10

Repository: nammayatri/shared-kernel

Length of output: 80


🏁 Script executed:

# Check if there's a hie.yaml or other config files that might specify versions
ls -la | grep -E "hie|stack|cabal|freeze"

Repository: nammayatri/shared-kernel

Length of output: 190


🏁 Script executed:

# Look for resolver specification or GHC version
rg "resolver|ghc-version" . -t yaml -t toml --max-count 10

Repository: nammayatri/shared-kernel

Length of output: 50


🏁 Script executed:

# Check the flake.lock for servant versions
cat flake.lock | grep -A 5 "servant-client\|servant-core" | head -50

Repository: nammayatri/shared-kernel

Length of output: 50


🏁 Script executed:

# Get a full view of what's in flake.lock
cat flake.lock | head -100

Repository: nammayatri/shared-kernel

Length of output: 2711


🏁 Script executed:

# Try a different approach - look for any nix files
find . -name "*.nix" -o -name "flake.nix" | head -10

Repository: nammayatri/shared-kernel

Length of output: 79


🏁 Script executed:

# Check the flake.nix file for servant and GHC versions
cat flake.nix | grep -A 5 -B 5 "servant\|ghc-version\|haskellPackages"

Repository: nammayatri/shared-kernel

Length of output: 50


🏁 Script executed:

# Get a better sense of the structure - look for haskellPackages or nixpkgs references
cat flake.nix | head -100

Repository: nammayatri/shared-kernel

Length of output: 2650


🏁 Script executed:

# Check if there's any mention of GHC version in flake.nix
cat flake.nix | grep -i "ghc\|haskell"

Repository: nammayatri/shared-kernel

Length of output: 811


🏁 Script executed:

# Look for any Haskell.nix or similar files that might specify versions
find . -name "*.nix" -type f | xargs grep -l "servant" 2>/dev/null | head -5

Repository: nammayatri/shared-kernel

Length of output: 50


🏁 Script executed:

# Let's examine the actual ClientError type to understand what show would output
rg "ClientError" lib/mobility-core/src/Kernel/External/Moengage/Moengage/Flow.hs -B 2 -A 2

Repository: nammayatri/shared-kernel

Length of output: 50


🏁 Script executed:

# Look at the imports in Flow.hs to understand what ClientError type is being used
head -50 lib/mobility-core/src/Kernel/External/Moengage/Moengage/Flow.hs

Repository: nammayatri/shared-kernel

Length of output: 1902


🏁 Script executed:

# Search for callAPI function definition to understand what errors it might return
rg "callAPI" lib/mobility-core/src/Kernel/External/Moengage/Moengage/ -A 10 | head -50

Repository: nammayatri/shared-kernel

Length of output: 818


🏁 Script executed:

# Look for callAPI in the broader codebase
rg "callAPI.*=" lib/mobility-core/src/ -A 3 | head -40

Repository: nammayatri/shared-kernel

Length of output: 4678


🏁 Script executed:

# Find where callAPI is defined
rg "^callAPI\s*::" lib/mobility-core/src/ -A 5 | head -40

Repository: nammayatri/shared-kernel

Length of output: 993


🏁 Script executed:

# Also search for callAPI definition
rg "callAPI\s+::" lib/mobility-core/src/ -B 2 -A 10 | head -60

Repository: nammayatri/shared-kernel

Length of output: 2162


🏁 Script executed:

# Search for the callAPI function signature more broadly
rg "callAPI.*::" lib/mobility-core/src/Kernel/ -A 5 | head -50

Repository: nammayatri/shared-kernel

Length of output: 4872


🏁 Script executed:

# Read the full callAPI' implementation to understand error handling
cat lib/mobility-core/src/Kernel/Utils/Servant/Client.hs | sed -n '100,200p'

Repository: nammayatri/shared-kernel

Length of output: 4186


🏁 Script executed:

# Look for the complete callAPI' function
rg "callAPI'" lib/mobility-core/src/Kernel/Utils/Servant/Client.hs -A 30 | head -80

Repository: nammayatri/shared-kernel

Length of output: 3398


🏁 Script executed:

# Check if there are similar patterns in the codebase where errors are handled differently
rg "fromEitherM.*show err" lib/mobility-core/src/ | head -20

Repository: nammayatri/shared-kernel

Length of output: 3269


🏁 Script executed:

# Look for other error handling patterns to see if const is used elsewhere
rg "fromEitherM.*const.*InternalError" lib/mobility-core/src/ | head -20

Repository: nammayatri/shared-kernel

Length of output: 50


🏁 Script executed:

# Check how other external API flows handle ClientError
rg ">>= fromEitherM" lib/mobility-core/src/Kernel/External/ -A 1 | head -40

Repository: nammayatri/shared-kernel

Length of output: 3117


Sanitize low-level error details from external API failures.

Line 49 exposes raw ClientError details via show err. While servant-client automatically redacts Authorization headers since v0.17, the show instance still includes other sensitive information (HTTP status codes, response bodies, URLs). For security hardening, replace with:

-  callAPI cfg.baseUrl (moengageClient cfg.appId cfg.appId (Just authToken) req) "moengageEvent" moengageEventAPI
-    >>= fromEitherM (\err -> InternalError $ "Failed to call Moengage Event API: " <> show err)
+  callAPI cfg.baseUrl (moengageClient cfg.appId cfg.appId (Just authToken) req) "moengageEvent" moengageEventAPI
+    >>= fromEitherM (const $ InternalError "Failed to call Moengage Event API")

Note: This pattern appears across multiple external API integrations (Wallet, Ticket, Tokenize, Insurance, Payment, Payout, Maps) and should be addressed consistently across the codebase.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
>>= fromEitherM (\err -> InternalError $ "Failed to call Moengage Event API: " <> show err)
>>= fromEitherM (const $ InternalError "Failed to call Moengage Event API")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/mobility-core/src/Kernel/External/Moengage/Moengage/Flow.hs` at line 49,
The current error mapping in Flow.hs uses fromEitherM (\err -> InternalError $
"Failed to call Moengage Event API: " <> show err), exposing raw ClientError
details; change it to return a generic, non-sensitive InternalError message
(e.g. "Failed to call Moengage Event API") without using show on the error, and
if needed capture/redact specific safe metadata in a separate audit/log path
(never inline the full show err). Update the mapping that produces InternalError
(the lambda passed to fromEitherM) to omit sensitive details and apply the same
replacement pattern to other external integrations (Wallet, Ticket, Tokenize,
Insurance, Payment, Payout, Maps) where fromEitherM or similar
error-to-InternalError conversions use show on low-level errors.

Add Moengage S2S API integration following the existing Kernel.External
service pattern. Includes service enum, encrypted config, Servant API
client, and pushEvent flow with Basic auth.
@witcher-shailesh witcher-shailesh force-pushed the backend/feat/moengage-s2s-integration branch from 688e5fa to 07114d6 Compare April 15, 2026 02:20
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.

1 participant