Skip to content

feat(event-process): EnricherService (ua-parser + geoip + bot detect) (EVO-1212)#40

Open
nickoliveira23 wants to merge 1 commit into
developfrom
feat/EVO-1212
Open

feat(event-process): EnricherService (ua-parser + geoip + bot detect) (EVO-1212)#40
nickoliveira23 wants to merge 1 commit into
developfrom
feat/EVO-1212

Conversation

@nickoliveira23

Copy link
Copy Markdown

Summary

Implements story 3.6 (Webhook Event Pipeline): a new EnricherService that enriches a received webhook envelope with parsed user-agent, IP geolocation and basic bot markers, before the event-process pipeline persists it.

  • EnricherService.enrich(envelope) => EnrichedEvent = the envelope plus { ua, geo, botMarkers } (the enrichment shape reused from EventsEnrichedContract).
  • UA parsed with ua-parser-js; geo resolved with the existing GeoLocationService (geoip-lite), reused as a provider to avoid coupling to the whole ClickTrackingModule.
  • Bot detection: small UA denylist (substring, case-insensitive). isDatacenter is false in the MVP (datacenter IP ranges deferred to Growth, per the card).

Recipient context (review follow-up, M1)

For email engagement events (open/click) the real recipient UA/IP live in the provider payload, not the HTTP envelope — the receiver fills headers/sourceIp from the inbound POST, which is the provider's own infra. RecipientSourceExtractor pulls the recipient UA/IP from the body per provider (sendgrid, mandrill, resend, mailersend, ses via SNS, sparkpost) and the enricher prefers it, falling back to the envelope when the body lacks it.

Scope notes

  • Array payloads (SendGrid/Mandrill batch many events per POST): the MVP reads the first event; per-event fan-out is a downstream normalization concern.
  • mailersend reuses the engagement heuristic as a best-effort (payload shape not verified against a real fixture).
  • EnricherService is registered but has no caller yet — the handle() wire-up (enrich -> writer.enqueue) lands in story 3.7 (EVO-1213) per the epic sequencing. This is a tested unit awaiting its consumer, not dead code.

Security

  • Pure service, no auth surface (internal pipeline). No external calls (geoip-lite is local).

Test plan

  • evo-flow: npm run typecheck — clean
  • evo-flow: npx jest src/runners/event-process/services/enricher.service.spec.ts src/runners/event-process/services/recipient-source.extractor.spec.ts — 18 examples, 0 failures
  • evo-flow: npx eslint <changed .ts> — clean

Covers ACs: iPhone UA -> device.type === 'mobile' (AC1), sourceIp 8.8.8.8 -> geo.country === 'US' (AC2, real geoip-lite), Googlebot -> isBot === true (AC3); plus body-over-envelope precedence and per-provider extraction.

Changed Files

  • src/runners/event-process/services/enricher.service.ts (new)
  • src/runners/event-process/services/enricher.service.spec.ts (new)
  • src/runners/event-process/services/recipient-source.extractor.ts (new)
  • src/runners/event-process/services/recipient-source.extractor.spec.ts (new)
  • src/runners/event-process/event-process.module.ts

Linked Issue

  • EVO-1212

…eo/bot (EVO-1212)

Implements story 3.6: enriches a received webhook envelope with parsed
user-agent (ua-parser-js), IP geolocation (reused GeoLocationService /
geoip-lite) and basic bot markers, before the pipeline persists it.

- EnricherService.enrich(envelope) => EnrichedEvent (envelope + { ua, geo, botMarkers })
- RecipientSourceExtractor: pulls the end-recipient UA/IP from the provider body
  (sendgrid, mandrill, resend, mailersend, ses/SNS, sparkpost) since for email
  engagement events the HTTP envelope only carries the provider's own infra;
  falls back to the envelope (header UA / sourceIp) when the body lacks it
- bot detection via a small UA denylist (substring, case-insensitive);
  isDatacenter is false in the MVP
- GeoLocationService reused as a provider (no ClickTrackingModule coupling)
- specs: enricher (AC1/AC2/AC3 + body-precedence + fallback) and extractor (per provider)

The handle() wire-up (enrich -> writer.enqueue) lands in story 3.7 (EVO-1213).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sorry @nickoliveira23, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

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