Backend for the heatmap moving entirely into identity (0.4 stage 5, część 2): encrypted-at-rest OAuth token storage (new token-crypto crate, migration, callback capture), three 24h background sync jobs (native via Gitea's new internal endpoint, GitHub GraphQL, GitLab REST — writing into contribution_day), and GraphQL (userHeatmap/setHeatmapPrivacy/myHeatmapPrivacy) with grant-based per-row visibility filtering for native repos. Also fixes a real pre-existing gap in contribution_day's unique constraint (Postgres NULL semantics silently allowed duplicates). GraphQL-level tests cover the security-relevant visibility paths.
Backend for the heatmap moving entirely into identity (0.4 stage 5, część 2): encrypted-at-rest OAuth token storage (new token-crypto crate, migration, callback capture), three 24h background sync jobs (native via Gitea's new internal endpoint, GitHub GraphQL, GitLab REST — writing into contribution_day), and GraphQL (userHeatmap/setHeatmapPrivacy/myHeatmapPrivacy) with grant-based per-row visibility filtering for native repos. Also fixes a real pre-existing gap in contribution_day's unique constraint (Postgres NULL semantics silently allowed duplicates). GraphQL-level tests cover the security-relevant visibility paths.
The GitHub/GitLab access (and refresh, when given) tokens from an OAuth
exchange were fetched once for the profile call and then dropped —
nothing persisted them. The new heatmap sync jobs (0.4 stage 5, część
2) need to call these providers' APIs later in the background, so the
callback now encrypts and stores them via a new syncode-identity-token-
crypto crate (ChaCha20-Poly1305, key from
SYNCODE_IDENTITY_TOKEN_ENCRYPTION_KEY). Drops the long-dead, never-
written linked_identity.token_ref column in favor of proper
access/refresh/expiry columns.
Three 24h tokio::spawn loops (mirroring the existing deletion sweep):
native pulls per-repo, per-day counts from Gitea's new internal
heatmap endpoint and resolves them into identity's own repository ids;
GitHub/GitLab pull from each provider's contribution API using the
tokens saved by the previous commit, splitting public/private per
provider's own shape (GitHub: diff against its unauthenticated public
events; GitLab: each event's project visibility directly). All three
upsert into contribution_day.
Also fixes a real pre-existing gap in that table's unique constraint:
Postgres never treats NULL as equal to NULL for uniqueness, and every
row here has source_provider_id or source_resource_id NULL by design,
so the original UNIQUE(...) silently allowed duplicates. A
COALESCE-to-sentinel index closes it.
userHeatmap(username) returns three layers: native (each day filtered
by whether the viewer can read that repository — public repos always,
private ones only with an active read grant, reusing the same
resolve_effective_capabilities machinery Etap 5 część 1 built), linked
public (always shown), and linked private (gated by
heatmap_privacy_setting's opt-in and audience). Works for an anonymous
caller too, since public data shouldn't require signing in to see.
setHeatmapPrivacy is a personal-setting mutation, checked by identity
rather than require_capability — nobody else's grant should be able to
flip someone's own privacy preference.
GraphQL-level tests cover the security-relevant paths: a private
repo's contributions are invisible without a grant and appear once one
exists, and linked-private honors both the opt-in and the audience.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Backend for the heatmap moving entirely into identity (0.4 stage 5, część 2): encrypted-at-rest OAuth token storage (new token-crypto crate, migration, callback capture), three 24h background sync jobs (native via Gitea's new internal endpoint, GitHub GraphQL, GitLab REST — writing into contribution_day), and GraphQL (userHeatmap/setHeatmapPrivacy/myHeatmapPrivacy) with grant-based per-row visibility filtering for native repos. Also fixes a real pre-existing gap in contribution_day's unique constraint (Postgres NULL semantics silently allowed duplicates). GraphQL-level tests cover the security-relevant visibility paths.