feat: ingest private events; surface public-only

The DB now stores everything GitHub will give us, the API only ever
returns public events (for now).

Endpoint switch in the github poller: when GITHUB_TOKEN is set we
hit /users/{u}/events (public + private), otherwise fall back to
/users/{u}/events/public. Either way each event's top-level `public`
boolean is captured into a new column.

Schema:
  migration 0003_event_public.sql adds events.public BOOLEAN NOT NULL
  DEFAULT true, plus an index on (public, occurred_at DESC).

Wire:
  Event gains a `public: bool` field.
  EventQuery gains `include_private: bool` (default false).
  list_events and source_summaries gate on it.
  moments-api pins include_private = false at every call site —
  threading it as a query param is a future-auth concern, not now.

The default-true on the column keeps existing rows correct: the 11
events already in the DB came from /events/public and are genuinely
public.

After this change, clear poller_state so the next worker run does a
fresh backfill via /events:

  DELETE FROM poller_state WHERE source = 'github';

Tests: +2 in github poller (private flag captured, default-public
on missing field) — 10 total green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-03 18:33:40 +03:00
parent 003f427e98
commit 3c0253519f
7 changed files with 78 additions and 11 deletions

View File

@@ -96,6 +96,9 @@ async fn list_events(
from: params.from,
to: params.to,
sources,
// Public timeline only — private events stay in the DB but are never
// surfaced. A future authenticated path can flip this.
include_private: false,
limit,
};
@@ -107,7 +110,11 @@ async fn list_events(
async fn list_sources(
State(state): State<AppState>,
) -> Result<Json<Vec<SourceSummary>>, ApiError> {
let summaries = state.store.source_summaries().await.map_err(internal)?;
let summaries = state
.store
.source_summaries(/* include_private */ false)
.await
.map_err(internal)?;
Ok(Json(summaries))
}