|
| 1 | +# Feedback API and browser package |
| 2 | + |
| 3 | +The system maps a site-defined resource key to one GitHub Discussion. Resource |
| 4 | +keys are stable and known by the consuming site; use category-qualified keys such |
| 5 | +as `articles/ranges`, `tips/vector-growth`, and `updates/2026-09`. The first path |
| 6 | +component selects a configured GitHub Discussion category. Unrecognised prefixes |
| 7 | +use `default_category`. |
| 8 | + |
| 9 | +There are two site modes: |
| 10 | + |
| 11 | +- `ranking` exposes batched native GitHub Discussion upvote counts. It may also |
| 12 | + expose explicitly selected reaction counts. It has no discussion-content API. |
| 13 | +- `discussion` can expose a full thread, post reactions, comments and replies, |
| 14 | + answers, polls, author associations, moderation state, labels, comment |
| 15 | + reactions/upvotes, and GitHub links. Each extra group is enabled by an intent. |
| 16 | + |
| 17 | +An unauthenticated visitor can read configured data but cannot mutate it. OAuth |
| 18 | +is initiated only by user interaction. The browser then talks directly to |
| 19 | +GitHub for viewer state and mutations; the service never receives the user's |
| 20 | +GitHub token after exchange and provides no general user-token proxy. |
| 21 | + |
| 22 | +## Configuration and intents |
| 23 | + |
| 24 | +Every site declares `mode`, `intents`, one or more categories, and a default |
| 25 | +category. `discussion_body` is the template used only when creating a thread and |
| 26 | +supports `{key}`, `{title}`, and `{url}`. |
| 27 | + |
| 28 | +| Intent | Returned or enabled data | |
| 29 | +| --- | --- | |
| 30 | +| `upvotes` | Native Discussion `upvoteCount`; required for `/reactions` | |
| 31 | +| `reactions` | Main-post reaction groups selected by `reaction_counters` | |
| 32 | +| `discussion` | Authoritative thread read and discussion creation | |
| 33 | +| `comments` | Comments and replies | |
| 34 | +| `answers` | Accepted-answer state | |
| 35 | +| `polls` | Poll question, options, and totals | |
| 36 | +| `authors` | Author identity and `authorAssociation` | |
| 37 | +| `moderation` | Minimized state and reason | |
| 38 | +| `comment_reactions` | Comment/reply reaction totals | |
| 39 | +| `comment_upvotes` | Comment/reply native upvote totals | |
| 40 | +| `labels` | Discussion labels | |
| 41 | +| `github_link` | Discussion/comment URLs and discussion number in counter results | |
| 42 | + |
| 43 | +Ranking mode accepts only `upvotes` and `reactions`. Discussion metadata intents |
| 44 | +require `discussion`; comment-specific metadata requires `comments`. Disabled |
| 45 | +features return 404 and do not add fields to GitHub queries or API responses. |
| 46 | + |
| 47 | +## HTTP routes |
| 48 | + |
| 49 | +All JSON responses contain `v: 1`. Errors are |
| 50 | +`{"v":1,"error":{"code":"...","message":"..."}}`. |
| 51 | + |
| 52 | +| Method | Route | Purpose | |
| 53 | +| --- | --- | --- | |
| 54 | +| `GET` | `/v1/sites/{site}/reactions?keys=a,b` | Sorted, deduplicated batch of up to `max_batch_size` counters | |
| 55 | +| `POST` | `/v1/sites/{site}/oauth/authorize` | Start PKCE OAuth with `challenge` and `nonce` | |
| 56 | +| `POST` | `/v1/sites/{site}/oauth/exchange` | Exchange `code`, `state`, and `verifier`; return token plus an origin-bound creation grant | |
| 57 | +| `POST` | `/v1/sites/{site}/discussions/ensure` | Find or create a thread for a validated resource and grant | |
| 58 | +| `GET` | `/v1/sites/{site}/discussion?keys=a` | Fetch one authoritative configured thread | |
| 59 | + |
| 60 | +A minimal counter response is: |
| 61 | + |
| 62 | +```json |
| 63 | +{"v":1,"site":"cpp-social","items":{"resources/42":{"id":"D_...","upvotes":17}}} |
| 64 | +``` |
| 65 | + |
| 66 | +`number` is included only for `github_link`; `reactions` is included only when |
| 67 | +`reaction_counters` is non-empty. Cache age and server internals are not exposed. |
| 68 | +Unknown resources have `id: null` and zero counts. Counter responses revalidate |
| 69 | +with an ETag. Discussion and OAuth responses are `no-store`. |
| 70 | + |
| 71 | +## Browser API |
| 72 | + |
| 73 | +`FeedbackClient.reactions(keys)` performs the multi-key service read. |
| 74 | +`createUpvoteControls()` creates accessible buttons, batches their initial read, |
| 75 | +authenticates on activation, creates a missing discussion lazily, and sends the |
| 76 | +native upvote directly to GitHub. `createAuthenticationStatus()` is deliberately |
| 77 | +independent so a site can mount login/status anywhere. |
| 78 | + |
| 79 | +The package also exports direct, typed GitHub helpers for comment/reply creation, |
| 80 | +comment editing/deletion, reactions, poll votes, accepted answers, native |
| 81 | +upvotes, and batched viewer-upvote state. A discussion UI can compose these |
| 82 | +without routing user actions through the service. Authentication tokens are held |
| 83 | +in session storage; counter/viewer snapshots contain no token. |
| 84 | + |
| 85 | +## GitHub request audit and abuse boundaries |
| 86 | + |
| 87 | +The server can contact GitHub only in these places: |
| 88 | + |
| 89 | +1. OAuth code exchange. State, PKCE, exact origins, expiry, and single-purpose |
| 90 | + creation grants bound the flow. |
| 91 | +2. GitHub App installation-token acquisition. Tokens are cached until shortly |
| 92 | + before expiry and acquisition is serialized. |
| 93 | +3. Counter refresh. Stale discussion IDs are grouped into `nodes(ids:)` batches |
| 94 | + of at most 50. Per-site in-flight work is shared, failures have a cooldown, |
| 95 | + global GitHub concurrency is capped, and maintenance sweeps are paced. |
| 96 | +4. Discussion discovery/creation. Work is serialized per site/resource; exact |
| 97 | + repository and category matches are required, and the resulting ID is stored. |
| 98 | +5. Discussion reads. A read is authoritative; simultaneous reads of the same |
| 99 | + thread share only the in-flight request. The response is never retained. |
| 100 | + |
| 101 | +Request sizes, key syntax, JSON fields, body sizes, origins, and batch size are |
| 102 | +bounded. Security headers are applied globally. Logs omit tokens, OAuth codes, |
| 103 | +comment bodies, URLs, origins, and client addresses. `--verbose` enables safe |
| 104 | +route timing and cache/GitHub-boundary diagnostics. |
| 105 | + |
| 106 | +The database stores discussion identity and aggregate counters only. It does not |
| 107 | +store discussion or comment bodies. Consequently a later request cannot serve a |
| 108 | +cached copy of deleted content; deleted nodes returned by GitHub are represented |
| 109 | +only by their deletion state, and frontend renderers must not render their body. |
0 commit comments