CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | /**
* Notifications + audit log helpers.
* Swallows DB failures so notifications never break the primary request path.
*/
import { db } from "../db";
import { notifications, auditLog } from "../db/schema";
export type NotificationKind =
| "mention"
| "review_requested"
| "pr_opened"
| "pr_merged"
| "pr_closed"
| "issue_opened"
| "issue_closed"
| "assigned"
| "ai_review"
| "gate_failed"
| "gate_repaired"
| "gate_passed"
| "security_alert"
| "deploy_success"
| "deploy_failed"
| "release_published"
| "repo_archived";
export async function notify(
userId: string,
opts: {
kind: NotificationKind;
title: string;
body?: string;
url?: string;
repositoryId?: string;
}
): Promise<void> {
try {
await db.insert(notifications).values({
userId,
kind: opts.kind,
title: opts.title,
body: opts.body,
url: opts.url,
repositoryId: opts.repositoryId,
});
} catch (err) {
console.error("[notify] failed:", err);
}
}
export async function notifyMany(
userIds: string[],
opts: {
kind: NotificationKind;
title: string;
body?: string;
url?: string;
repositoryId?: string;
}
): Promise<void> {
const unique = Array.from(new Set(userIds));
if (unique.length === 0) return;
try {
await db.insert(notifications).values(
unique.map((userId) => ({
userId,
kind: opts.kind,
title: opts.title,
body: opts.body,
url: opts.url,
repositoryId: opts.repositoryId,
}))
);
} catch (err) {
console.error("[notify] batch failed:", err);
}
}
export async function audit(opts: {
userId?: string | null;
repositoryId?: string | null;
action: string;
targetType?: string;
targetId?: string;
ip?: string;
userAgent?: string;
metadata?: Record<string, unknown>;
}): Promise<void> {
try {
await db.insert(auditLog).values({
userId: opts.userId ?? null,
repositoryId: opts.repositoryId ?? null,
action: opts.action,
targetType: opts.targetType,
targetId: opts.targetId,
ip: opts.ip,
userAgent: opts.userAgent,
metadata: opts.metadata ? JSON.stringify(opts.metadata) : null,
});
} catch (err) {
// Audit must never break the primary flow
console.error("[audit] failed:", err);
}
}
|