feat(telegram): notify on Gitea issue creation
apps/web/src/lib/telegram.ts: server-side Telegram client + alertIssueCreated. createGithubIssueForInsight action fires the alert after issue is created (after audit log, before revalidate). Severity emoji + insight link + issue link. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import { auth } from "@/lib/auth";
|
||||
import { writeAudit } from "@/lib/audit";
|
||||
import { createIssue, buildIssueBody } from "@/lib/gitea";
|
||||
import { pipelineQueue } from "@/lib/queue";
|
||||
import { alertIssueCreated } from "@/lib/telegram";
|
||||
|
||||
const ALLOWED_STATUS = new Set([
|
||||
"new",
|
||||
@@ -161,9 +162,21 @@ export async function createGithubIssueForInsight(
|
||||
await writeAudit({
|
||||
endpoint: `/insights/${insightId}/github-issue`,
|
||||
method: "POST",
|
||||
requestPayload: { issueNumber: issue.number, repo: process.env[`GITHUB_REPO_${insight.projectKey.toUpperCase()}`] },
|
||||
requestPayload: { issueNumber: issue.number, repo: process.env[`GITEA_REPO_${insight.projectKey.toUpperCase()}`] },
|
||||
responseStatus: 200,
|
||||
});
|
||||
|
||||
// Telegram notification (fire-and-forget)
|
||||
void alertIssueCreated({
|
||||
insightId: insight.id,
|
||||
insightTitle: insight.title,
|
||||
severity: insight.severity,
|
||||
type: insight.type,
|
||||
issueNumber: issue.number,
|
||||
issueUrl: issue.html_url,
|
||||
panelUrl,
|
||||
});
|
||||
|
||||
revalidatePath("/insights");
|
||||
revalidatePath(`/insights/i/${insightId}`);
|
||||
return { url: issue.html_url, number: issue.number };
|
||||
|
||||
61
apps/web/src/lib/telegram.ts
Normal file
61
apps/web/src/lib/telegram.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
// Server-side Telegram client for the web app (issue creation, manual triggers, etc).
|
||||
// Fire-and-forget; failures swallowed.
|
||||
|
||||
const TOKEN = process.env.TELEGRAM_BOT_TOKEN ?? "";
|
||||
const CHAT_ID = process.env.TELEGRAM_CHAT_ID ?? "";
|
||||
|
||||
export function isTelegramConfigured(): boolean {
|
||||
return Boolean(TOKEN && CHAT_ID);
|
||||
}
|
||||
|
||||
export async function sendTelegram(opts: {
|
||||
text: string;
|
||||
silent?: boolean;
|
||||
parseMode?: "HTML" | "Markdown";
|
||||
}): Promise<{ ok: boolean; error?: string }> {
|
||||
if (!isTelegramConfigured()) return { ok: false, error: "not_configured" };
|
||||
try {
|
||||
const res = await fetch(`https://api.telegram.org/bot${TOKEN}/sendMessage`, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
chat_id: CHAT_ID,
|
||||
text: opts.text,
|
||||
parse_mode: opts.parseMode ?? "HTML",
|
||||
disable_notification: opts.silent ?? false,
|
||||
disable_web_page_preview: true,
|
||||
}),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const err = await res.text();
|
||||
return { ok: false, error: `${res.status}: ${err.slice(0, 200)}` };
|
||||
}
|
||||
return { ok: true };
|
||||
} catch (e) {
|
||||
return { ok: false, error: (e as Error).message };
|
||||
}
|
||||
}
|
||||
|
||||
export function alertIssueCreated(opts: {
|
||||
insightId: string;
|
||||
insightTitle: string;
|
||||
severity: string;
|
||||
type: string;
|
||||
issueNumber: number;
|
||||
issueUrl: string;
|
||||
panelUrl: string;
|
||||
}): Promise<{ ok: boolean; error?: string }> {
|
||||
const sev = opts.severity === "P0" || opts.severity === "P1" ? "🔴" : opts.severity === "P2" ? "🟡" : "⚪";
|
||||
const text = [
|
||||
`🎟️ <b>Gitea issue opened</b>`,
|
||||
`${sev} <b>${opts.severity}</b> · ${escapeHtml(opts.type)}`,
|
||||
`<b>${escapeHtml(opts.insightTitle)}</b>`,
|
||||
``,
|
||||
`<a href="${opts.issueUrl}">#${opts.issueNumber} on Gitea</a> · <a href="${opts.panelUrl}/insights/i/${opts.insightId}">Insight in panel</a>`,
|
||||
].join("\n");
|
||||
return sendTelegram({ text });
|
||||
}
|
||||
|
||||
function escapeHtml(s: string): string {
|
||||
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user