Pular para o conteúdo
He4rt / Docs
Toggle sidebar
Interno noindex

Referência técnica — acessível, mas fora dos buscadores.

Discord
Engenharia Leitura · 5 min Integration Github
Aceito
04/06/2026 documento interno · noindex

GitHub Community Contributions

Status: Accepted Date: 2026-06-04 Deciders: Clintonrocha98

Context

The community wanted a recurring presentation ("Quem fez a He4rt bater") showing who is contributing across the community's public GitHub repos. The first attempt was a provisional artisan command (community:retrospective) that ran gh live, aggregated a JSON in-memory and uploaded it to a paste host. It did not persist anything, ran outside the modular architecture (under app/Retrospective/), and only worked for a single repo at a time.

We need a persistent, queryable model that:

The existing activity domain has an Interaction model, but it is gamification-coupled: it requires a character_id (a registered member) and a tenant_id, and carries coins/xp. It cannot represent a GitHub contributor who never joined the platform.

Decision

Build the ingestion in integration-github (transport), mirroring the Discord data-lake pattern.

1. Own contribution model, not Interaction

A normalized github_contributions table keyed by GitHub login/id (member-or-not), with unique(tenant_id, repo, type, external_ref) for idempotency. Gamification is left as a seam: this module emits GithubContributionRecorded and does not depend on activity/economy.

2. Normalized read model + raw lake

github_contributions is the read model the presentation queries. github_event_logs is the raw, append-only webhook lake (deduped by delivery id) for audit/replay — exactly mirroring discord_event_logs.

3. Split ingestion paths

4. Panel-managed allowlist, scoped per tenant

github_repositories (managed in panel-admin) is the source of truth for which repos count. Each repo belongs to a tenant (unique(tenant_id, full_name)): the allowlist is per community, and the same public repo may be tracked by more than one tenant. The org webhook delivers everything; ingestion filters by the allowlist and fans out to every tenant tracking the repo.

4b. Tenant isolation

Both github_repositories and github_contributions carry tenant_id; the presentation, the panel resource and the live/backfill writers are all scoped by it. The raw lake (github_event_logs) stays global — one delivery is stored once and projected to N tenants. Because public GitHub data is identical across communities, a shared repo's contributions are deliberately duplicated per tenant (one row each) to keep every community's read model fully isolated and independently queryable. The public retrospective resolves the tenant from a route slug (/comunidade/{tenant}/retrospectiva), defaulting to config('he4rt.main_tenant') when none is given.

5. Store everything, filter on read

Everything is stored raw. The presentation excludes only bots at read time. Closed-unmerged PRs count as participation (consistent with the "all contributors" audience), but are broken out by outcome (prs_merged / prs_unmerged) so the view distinguishes work that landed from work that was rejected/abandoned. Because filtering lives only in the read model, this refinement was a one-function change with no re-backfill — exactly the payoff of storing everything raw.

6. Auth and scope

A fine-grained PAT (integration-github.api_token) authenticates the REST connector for backfill; a manually-registered org webhook with a shared secret (integration-github.webhook_secret) feeds the live stream. Contribution types in scope: PR, review, issue, comment, commit (no reactions — GitHub has no reaction webhook).

Consequences