fix(discovery): switch runtime from GitHub API to Forgejo API #5

Merged
slaid098 merged 6 commits from fix/discovery/forgejo-api-migration into main 2026-08-07 13:34:33 +03:00
Owner

Что сделано

Перевёл рантайм discovery с GitHub API на Forgejo API (git.slaid098.dev). Логика (fetch list → detail → raw README + cover-meta → parse → render) не изменилась — только URL-шаблоны, заголовки и webhook.

  • src/lib/git-host.ts (новый) — единый конфиг хоста: FORGEJO_URL, FORGEJO_API, SHOWCASE_OWNER, FORGEJO_TOKEN + хелпер forgejoHeaders() (Authorization: token {token}, Accept: application/json).
  • src/lib/readme-discovery.ts — эндпоинты Forgejo API v1: список GET /api/v1/users/{owner}/repos?limit=50 (клиентский фильтр !r.private), detail GET /api/v1/repos/{owner}/{repo}, raw README GET /api/v1/repos/{owner}/{repo}/raw/README.md?ref={branch}, cover SHA GET /api/v1/repos/{owner}/{repo}/contents/assets/cover.png?ref={branch}. Заголовки через forgejoHeaders().
  • src/app/apps/[slug]/cover/route.ts — raw cover GET /api/v1/repos/{owner}/{repo}/raw/assets/cover.png?ref={branch}.
  • src/lib/showcase.tsSHOWCASE_OWNER импортируется из git-host.ts, FORGEJO_TOKEN вместо GITHUB_TOKEN.
  • src/app/api/webhook/route.tsx-github-eventx-gitea-event, x-hub-signature-256 (hex с префиксом sha256=) → x-gitea-signature (hex без префикса), env GITHUB_WEBHOOK_SECRETWEBHOOK_SECRET. HMAC + timingSafeEqual с guard равных длин сохранён. Event filter: push (default branch + README.md/cover.png в commits) / repository/public (showcase) / other skip.
  • src/components/site-footer.tsx — ссылка на https://git.slaid098.dev/slaid098.
  • src/components/qa-chat.tsxPROJECT_LINKS → Forgejo URL.
  • src/lib/support.tsGITHUB_URLFORGEJO_PROFILE_URL (Forgejo URL, @public сохранён).
  • src/lib/i18n.tsfooter.githubfooter.repo («Forgejo»), appDetail.githubappDetail.repo («Репозиторий →»/«Repository →»).
  • src/app/[locale]/[slug]/page.tsx — хардкод «GitHub →» заменён на d.appDetail.repo.
  • .env.exampleGITHUB_TOKENFORGEJO_TOKEN, GITHUB_WEBHOOK_SECRETWEBHOOK_SECRET, добавлен FORGEJO_URL=https://git.slaid098.dev.
  • scripts/spike-discovery.tsGITHUB_TOKENFORGEJO_TOKEN.
  • Тесты (src/__tests__/) — hostname-матчеры (api.github.com/raw.githubusercontent.comgit.slaid098.dev//api/v1), URL-паттерны (raw /raw/...?ref=, contents /contents/...?ref=), имена webhook-заголовков (x-gitea-event/x-gitea-signature), env-имена (WEBHOOK_SECRET), htmlUrl на git.slaid098.dev/slaid098/.... Добавлен тест на фильтр приватных репо.

Почему

GitHub-аккаунт slaid098 забанен, api.github.com/raw.githubusercontent.com мертвы. Git remote уже на Forgejo (git.slaid098.dev). Discovery нейтрален к хосту — достаточно было сменить URL-шаблоны и заголовки, не трогая парсинг/рендер.

Watch out

  • Webhook в Forgejo: нужно перенастроить webhook URL на тот же /api/webhook и убедиться, что secret в Forgejo = WEBHOOK_SECRET env. Forgejo шлёт X-Gitea-Event/X-Gitea-Signature (hex без префикса) — код уже обновлён.
  • FORGEJO_TOKEN: должен иметь read-доступ к публичным репо slaid098/*. Для публичных репо токен опционален, но без него Forgejo может rate-limit'ить.
  • Forgejo visibility param: у Forgejo параметр visibility в /users/{owner}/repos может не работать — фильтр публичности (!r.private) сделан на стороне клиента.
  • i18n key rename: footer.githubfooter.repo, appDetail.githubappDetail.repo — если есть внешние потребители i18n-словаря, нужно обновить.
  • docs/ — исторические handoff/decision документы (ADR-014, ADR-021, PR#96 и др.) оставлены как есть (историческая запись GitHub-эпохи).

Pending

  • Обновить webhook в Forgejo UI (URL тот же, secret = WEBHOOK_SECRET).
  • Прописать FORGEJO_URL, FORGEJO_TOKEN, WEBHOOK_SECRET в Vercel env (убрать старые GITHUB_TOKEN, GITHUB_WEBHOOK_SECRET).

Closes #2

## Что сделано Перевёл рантайм discovery с GitHub API на Forgejo API (`git.slaid098.dev`). Логика (fetch list → detail → raw README + cover-meta → parse → render) не изменилась — только URL-шаблоны, заголовки и webhook. - **`src/lib/git-host.ts`** (новый) — единый конфиг хоста: `FORGEJO_URL`, `FORGEJO_API`, `SHOWCASE_OWNER`, `FORGEJO_TOKEN` + хелпер `forgejoHeaders()` (`Authorization: token {token}`, `Accept: application/json`). - **`src/lib/readme-discovery.ts`** — эндпоинты Forgejo API v1: список `GET /api/v1/users/{owner}/repos?limit=50` (клиентский фильтр `!r.private`), detail `GET /api/v1/repos/{owner}/{repo}`, raw README `GET /api/v1/repos/{owner}/{repo}/raw/README.md?ref={branch}`, cover SHA `GET /api/v1/repos/{owner}/{repo}/contents/assets/cover.png?ref={branch}`. Заголовки через `forgejoHeaders()`. - **`src/app/apps/[slug]/cover/route.ts`** — raw cover `GET /api/v1/repos/{owner}/{repo}/raw/assets/cover.png?ref={branch}`. - **`src/lib/showcase.ts`** — `SHOWCASE_OWNER` импортируется из `git-host.ts`, `FORGEJO_TOKEN` вместо `GITHUB_TOKEN`. - **`src/app/api/webhook/route.ts`** — `x-github-event` → `x-gitea-event`, `x-hub-signature-256` (hex с префиксом `sha256=`) → `x-gitea-signature` (hex без префикса), env `GITHUB_WEBHOOK_SECRET` → `WEBHOOK_SECRET`. HMAC + `timingSafeEqual` с guard равных длин сохранён. Event filter: `push` (default branch + README.md/cover.png в commits) / `repository`/`public` (showcase) / other skip. - **`src/components/site-footer.tsx`** — ссылка на `https://git.slaid098.dev/slaid098`. - **`src/components/qa-chat.tsx`** — `PROJECT_LINKS` → Forgejo URL. - **`src/lib/support.ts`** — `GITHUB_URL` → `FORGEJO_PROFILE_URL` (Forgejo URL, `@public` сохранён). - **`src/lib/i18n.ts`** — `footer.github` → `footer.repo` («Forgejo»), `appDetail.github` → `appDetail.repo` («Репозиторий →»/«Repository →»). - **`src/app/[locale]/[slug]/page.tsx`** — хардкод «GitHub →» заменён на `d.appDetail.repo`. - **`.env.example`** — `GITHUB_TOKEN` → `FORGEJO_TOKEN`, `GITHUB_WEBHOOK_SECRET` → `WEBHOOK_SECRET`, добавлен `FORGEJO_URL=https://git.slaid098.dev`. - **`scripts/spike-discovery.ts`** — `GITHUB_TOKEN` → `FORGEJO_TOKEN`. - **Тесты** (`src/__tests__/`) — hostname-матчеры (`api.github.com`/`raw.githubusercontent.com` → `git.slaid098.dev`/`/api/v1`), URL-паттерны (raw `/raw/...?ref=`, contents `/contents/...?ref=`), имена webhook-заголовков (`x-gitea-event`/`x-gitea-signature`), env-имена (`WEBHOOK_SECRET`), `htmlUrl` на `git.slaid098.dev/slaid098/...`. Добавлен тест на фильтр приватных репо. ## Почему GitHub-аккаунт `slaid098` забанен, `api.github.com`/`raw.githubusercontent.com` мертвы. Git remote уже на Forgejo (`git.slaid098.dev`). Discovery нейтрален к хосту — достаточно было сменить URL-шаблоны и заголовки, не трогая парсинг/рендер. ## Watch out - **Webhook в Forgejo**: нужно перенастроить webhook URL на тот же `/api/webhook` и убедиться, что secret в Forgejo = `WEBHOOK_SECRET` env. Forgejo шлёт `X-Gitea-Event`/`X-Gitea-Signature` (hex без префикса) — код уже обновлён. - **`FORGEJO_TOKEN`**: должен иметь read-доступ к публичным репо `slaid098/*`. Для публичных репо токен опционален, но без него Forgejo может rate-limit'ить. - **Forgejo `visibility` param**: у Forgejo параметр `visibility` в `/users/{owner}/repos` может не работать — фильтр публичности (`!r.private`) сделан на стороне клиента. - **i18n key rename**: `footer.github` → `footer.repo`, `appDetail.github` → `appDetail.repo` — если есть внешние потребители i18n-словаря, нужно обновить. - **docs/** — исторические handoff/decision документы (ADR-014, ADR-021, PR#96 и др.) оставлены как есть (историческая запись GitHub-эпохи). ## Pending - Обновить webhook в Forgejo UI (URL тот же, secret = `WEBHOOK_SECRET`). - Прописать `FORGEJO_URL`, `FORGEJO_TOKEN`, `WEBHOOK_SECRET` в Vercel env (убрать старые `GITHUB_TOKEN`, `GITHUB_WEBHOOK_SECRET`). Closes #2
Author
Owner

Code Review Summary

Чистая миграция discovery с GitHub API на Forgejo API: URL-шаблоны, заголовки и webhook обновлены, парсинг/рендер не тронут. Все cross-file связи (git-host.ts→cover/discovery/showcase, i18n.ts→footer/page, .env.example→consumers) обновлены в одном PR — missing paired updates нет. Тесты полностью переведены на Forgejo-матчеры + добавлен тест на фильтр приватных репо.

Positives

  • src/lib/git-host.ts — отличная дедупликация: makeGithubHeaders (был в 2 файлах) → единый forgejoHeaders() с optional token. Конфиг хоста в одном месте (FORGEJO_URL/FORGEJO_API/SHOWCASE_OWNER/FORGEJO_TOKEN).
  • src/app/api/webhook/route.ts:13-20verifySignature корректен: guard равных длин (a.length !== b.length) ПЕРЕД timingSafeEqual (предотвращает throw), hex без префикса sha256=. revalidateTag в try/catch → 200 всегда (не ломает webhook при cache backend down).
  • src/lib/readme-discovery.ts:202 — self-exclusion r.name !== "slaid098-dev" сохранён, добавлен клиентский фильтр !r.private (Forgejo visibility param ненадёжен).
  • ISR сохранён — все fetch'и в fetchCoverMeta/fetchReadme/discoverShowcaseRepos сохранили cache: "force-cache" + next:{revalidate:3600, tags:["showcase"]}.
  • fetchCoverMeta возвращает { exists, sha } (blob SHA для cache-busting, ADR-022) — контракт сохранён.
  • src/lib/support.ts:25-28@public JSDoc перенесён с GITHUB_URL на FORGEJO_PROFILE_URL (Knip не ругается).
  • noUncheckedIndexedAccess + exactOptionalPropertyTypes — guards соблюдены: ?? "", ?? null, default_branch?: string в cover route, data.sha проверяется typeof === "string".
  • Тесты — все матчеры обновлены (api.github.com/raw.githubusercontent.comgit.slaid098.dev//api/v1), webhook-заголовки (x-gitea-event/x-gitea-signature без префикса), env (WEBHOOK_SECRET). Добавлен тест filters out private repos.

Suggestions (info, not blocking)

  • src/lib/support.ts:28 [duplication] FORGEJO_PROFILE_URL экспортируется, но footer (site-footer.tsx:22) хардкодит тот же URL https://git.slaid098.dev/slaid098. Это существующее дублирование (было с GITHUB_URL) — можно подключить footer к константе в отдельном PR.
  • src/lib/readme-discovery.ts:188-269 [style] discoverShowcaseRepos ~80 строк (TS standard — <30). Существующая функция, структурно не изменена — не регрессия. Можно разбить на fetchRepoDetail + buildDiscoveredRepo в будущем.

Verdict: APPROVE

## Code Review Summary Чистая миграция discovery с GitHub API на Forgejo API: URL-шаблоны, заголовки и webhook обновлены, парсинг/рендер не тронут. Все cross-file связи (git-host.ts→cover/discovery/showcase, i18n.ts→footer/page, .env.example→consumers) обновлены в одном PR — missing paired updates нет. Тесты полностью переведены на Forgejo-матчеры + добавлен тест на фильтр приватных репо. ### Positives - **`src/lib/git-host.ts`** — отличная дедупликация: `makeGithubHeaders` (был в 2 файлах) → единый `forgejoHeaders()` с optional token. Конфиг хоста в одном месте (`FORGEJO_URL`/`FORGEJO_API`/`SHOWCASE_OWNER`/`FORGEJO_TOKEN`). - **`src/app/api/webhook/route.ts:13-20`** — `verifySignature` корректен: guard равных длин (`a.length !== b.length`) ПЕРЕД `timingSafeEqual` (предотвращает throw), hex без префикса `sha256=`. `revalidateTag` в try/catch → 200 всегда (не ломает webhook при cache backend down). - **`src/lib/readme-discovery.ts:202`** — self-exclusion `r.name !== "slaid098-dev"` сохранён, добавлен клиентский фильтр `!r.private` (Forgejo `visibility` param ненадёжен). - **ISR сохранён** — все fetch'и в `fetchCoverMeta`/`fetchReadme`/`discoverShowcaseRepos` сохранили `cache: "force-cache"` + `next:{revalidate:3600, tags:["showcase"]}`. - **`fetchCoverMeta`** возвращает `{ exists, sha }` (blob SHA для cache-busting, ADR-022) — контракт сохранён. - **`src/lib/support.ts:25-28`** — `@public` JSDoc перенесён с `GITHUB_URL` на `FORGEJO_PROFILE_URL` (Knip не ругается). - **`noUncheckedIndexedAccess` + `exactOptionalPropertyTypes`** — guards соблюдены: `?? ""`, `?? null`, `default_branch?: string` в cover route, `data.sha` проверяется `typeof === "string"`. - **Тесты** — все матчеры обновлены (`api.github.com`/`raw.githubusercontent.com` → `git.slaid098.dev`/`/api/v1`), webhook-заголовки (`x-gitea-event`/`x-gitea-signature` без префикса), env (`WEBHOOK_SECRET`). Добавлен тест `filters out private repos`. ### Suggestions (info, not blocking) - **src/lib/support.ts:28** [duplication] `FORGEJO_PROFILE_URL` экспортируется, но footer (`site-footer.tsx:22`) хардкодит тот же URL `https://git.slaid098.dev/slaid098`. Это существующее дублирование (было с `GITHUB_URL`) — можно подключить footer к константе в отдельном PR. - **src/lib/readme-discovery.ts:188-269** [style] `discoverShowcaseRepos` ~80 строк (TS standard — <30). Существующая функция, структурно не изменена — не регрессия. Можно разбить на `fetchRepoDetail` + `buildDiscoveredRepo` в будущем. ### Verdict: APPROVE
slaid098 deleted branch fix/discovery/forgejo-api-migration 2026-08-07 13:34:37 +03:00
Sign in to join this conversation.
No reviewers
No labels
bug
performance
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
slaid098/slaid098-dev!5
No description provided.