Skip to content

PostHog server-side events (Phase 9.6 backend) #20

Description

@pablo-clueless

Scope

Capture the money events server-side so they survive ad-blockers. Frontend PostHog is a separate (frontend-repo) task.

Events

Event Fired from
member_activated activation pipeline (webhook + manual + orphan replay)
post_approved Content admin approval
sponsor_activated Stripe checkout.session.completed handler
invoice_paid Stripe invoice.paid handler

Rules

  • PostHog .NET client behind a SharedKernel-owned interface (e.g. IAnalytics.Capture(event, distinctId, props)); a no-op implementation registers when POSTHOG_API_KEY is absent — local dev must not need PostHog.
  • Fire-and-forget: analytics failures are logged, never affect the request path.
  • Distinct ids: member id for member events, sponsor id for sponsor events. No emails or PII in properties.
  • Add key to env.example; Serilog remains the logging story — this is product analytics only.

Dependencies

None hard. post_approved lands with #4, sponsor events with #16 — implement the interface + member_activated now, and each later ticket adds its own capture call (note this in those PRs).

Acceptance criteria

  • No-op implementation when unconfigured (asserted in tests)
  • member_activated captured for all three activation paths
  • Failures logged, never thrown

Conventions (project-wide, non-negotiable)

  • .NET 9, records for immutable shapes, file-scoped namespaces, primary constructors where they read well. Minimal-API endpoints grouped per module via IEndpointModule.MapEndpoints.
  • Result<T> (SharedKernel) instead of exception-driven control flow. Endpoint results map failures to ProblemDetails with the stable error codes listed above — the frontend keys off them.
  • Module owns its EF Core DbContext mapped to its own Postgres schema. Modules never reference each other's internals — cross-module needs go through a public contract interface or an in-process domain event (IEventPublisher).
  • All external calls (GitHub, Stripe, Brevo, Cloudinary, Meilisearch) behind interfaces owned by the consuming module.
  • Every list endpoint paginated (offset is fine). xUnit tests in tests/CommunityPro.Tests/<Module>/ following the existing harness patterns (see Members/MembersTestHarness.cs).

Metadata

Metadata

Assignees

No one assigned

    Labels

    cross-cuttingCross-cutting backend work

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions