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
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).
Scope
Capture the money events server-side so they survive ad-blockers. Frontend PostHog is a separate (frontend-repo) task.
Events
member_activatedpost_approvedsponsor_activatedcheckout.session.completedhandlerinvoice_paidinvoice.paidhandlerRules
IAnalytics.Capture(event, distinctId, props)); a no-op implementation registers whenPOSTHOG_API_KEYis absent — local dev must not need PostHog.env.example; Serilog remains the logging story — this is product analytics only.Dependencies
None hard.
post_approvedlands with #4, sponsor events with #16 — implement the interface +member_activatednow, and each later ticket adds its own capture call (note this in those PRs).Acceptance criteria
member_activatedcaptured for all three activation pathsConventions (project-wide, non-negotiable)
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.DbContextmapped 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).tests/CommunityPro.Tests/<Module>/following the existing harness patterns (seeMembers/MembersTestHarness.cs).