refactor(integrations): migrate issue tracker GitHub → Gitea
- apps/web/src/lib/gitea.ts: createIssue / getIssue / verifyWebhookSignature / buildIssueBody
* Endpoint: git.semih.ai/api/v1 (configurable via GITEA_BASE_URL)
* Auth: 'Authorization: token <PAT>' (Gitea convention)
* Labels: Gitea expects numeric IDs not strings → ensureLabels() resolves/creates
with color coding (P0/P1 red, P2 yellow, P3 green, type-* grey, default blue)
* Webhook signature: X-Gitea-Signature (hex, no sha256= prefix)
- apps/worker/src/lib/gitea.ts: read-only getIssue() for sync polling
- _actions.ts + github-sync.ts now import from /lib/gitea
- Removed old apps/{web,worker}/.../lib/github.ts + /api/webhooks/github route
(the receiver was already dead — sp.semih.ai is Tailscale-only)
- UI: 'GitHub' label → 'Gitea' on insight detail card
- github-sync job filters by githubIssueUrl.startsWith(GITEA_BASE_URL) so legacy
GitHub-hosted insights (semihyesilyurt/sase.tr#20) stay frozen rather than
collide with same-numbered Gitea issues at root/sase.tr.
Env migration (Coolify, panel-web + panel-worker):
- removed: GITHUB_TOKEN, GITHUB_REPO_SASE, GITHUB_WEBHOOK_SECRET
- added: GITEA_TOKEN, GITEA_REPO_SASE=root/sase.tr, GITEA_BASE_URL=https://git.semih.ai
Provisioned Gitea PAT 'super-panel-insights' (scopes: write:repository + write:issue),
stored in Bitwarden.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { prisma } from "../db";
|
||||
import { getIssue } from "../lib/github";
|
||||
import { getIssue } from "../lib/gitea";
|
||||
|
||||
const POLL_LIMIT = Number(process.env.GITHUB_SYNC_LIMIT ?? "50");
|
||||
|
||||
@@ -13,9 +13,13 @@ export type GithubSyncResult = {
|
||||
// Used because the panel is Tailscale-only and can't receive webhooks.
|
||||
export async function runGithubSync(): Promise<GithubSyncResult> {
|
||||
// Only poll insights that have an issue and could still change state.
|
||||
const giteaBase = (process.env.GITEA_BASE_URL ?? "https://git.semih.ai").replace(/\/$/, "");
|
||||
const tracked = await prisma.insight.findMany({
|
||||
where: {
|
||||
githubIssueNumber: { not: null },
|
||||
// Only sync issues hosted on the configured Gitea instance.
|
||||
// Old GitHub-hosted insights stay frozen until manually re-linked or dismissed.
|
||||
githubIssueUrl: { startsWith: giteaBase },
|
||||
status: { notIn: ["validated", "dismissed", "duplicate"] },
|
||||
},
|
||||
orderBy: { updatedAt: "asc" },
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// Minimal GitHub REST client for the worker (read-only).
|
||||
// Outbound only — used because Tailscale-only panel can't receive GitHub webhooks.
|
||||
// Minimal Gitea REST client for the worker (read-only).
|
||||
// Outbound only — used because Tailscale-only panel can't receive webhooks.
|
||||
|
||||
const TOKEN = process.env.GITHUB_TOKEN ?? "";
|
||||
const TOKEN = process.env.GITEA_TOKEN ?? "";
|
||||
const BASE = (process.env.GITEA_BASE_URL ?? "https://git.semih.ai").replace(/\/$/, "");
|
||||
|
||||
function repoFor(projectKey: string): { owner: string; name: string } | null {
|
||||
const env = process.env[`GITHUB_REPO_${projectKey.toUpperCase()}`];
|
||||
const env = process.env[`GITEA_REPO_${projectKey.toUpperCase()}`];
|
||||
if (!env) return null;
|
||||
const [owner, name] = env.split("/");
|
||||
if (!owner || !name) return null;
|
||||
@@ -20,11 +21,10 @@ export async function getIssue(
|
||||
if (!TOKEN) return null;
|
||||
const repo = repoFor(projectKey);
|
||||
if (!repo) return null;
|
||||
const res = await fetch(`https://api.github.com/repos/${repo.owner}/${repo.name}/issues/${number}`, {
|
||||
const res = await fetch(`${BASE}/api/v1/repos/${repo.owner}/${repo.name}/issues/${number}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${TOKEN}`,
|
||||
Accept: "application/vnd.github+json",
|
||||
"X-GitHub-Api-Version": "2022-11-28",
|
||||
Authorization: `token ${TOKEN}`,
|
||||
Accept: "application/json",
|
||||
},
|
||||
});
|
||||
if (!res.ok) return null;
|
||||
Reference in New Issue
Block a user