TypeScript 97.5%
SQL 1.4%
Python 0.8%
1import { newId } from "@fetcha/core";2import { abuseEvents, db } from "@fetcha/db";3import { getEmailService } from "@fetcha/email";4import type { ApiPrincipal } from "../auth";5import type { MonthlyUsage } from "../limits";6import { getKV } from "../redis";7import { formatUsd } from "./pricing";89function monthKey(): string {10 const d = new Date();11 return `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, "0")}`;12}1314/** Send soft-limit alerts once per project/org per month (idempotent via KV flags). */15export async function checkSpendAlerts(p: ApiPrincipal, usage: MonthlyUsage): Promise<void> {16 const kv = getKV();17 const email = getEmailService();18 const checks: Array<{ key: string; spent: number; limit: number | null; label: string }> = [19 { key: `fch:alert:soft:proj:${p.projectId}:${monthKey()}`, spent: usage.projectSpendUsd, limit: p.projectSoftLimitUsd, label: p.projectName },20 { key: `fch:alert:soft:org:${p.organizationId}:${monthKey()}`, spent: usage.spendUsd, limit: p.orgSoftLimitUsd, label: p.organizationName },21 ];22 for (const c of checks) {23 if (c.limit === null || c.limit <= 0 || c.spent < c.limit) continue;24 if (await kv.get(c.key).catch(() => "1")) continue;25 await kv.set(c.key, "1", 40 * 86_400).catch(() => {});26 email.sendUsageAlert(p.ownerEmail, { projectName: c.label, spent: formatUsd(c.spent), limit: formatUsd(c.limit), kind: "soft" }).catch(() => {});27 }28}2930/** Notify once per month when a hard limit blocks requests. */31export async function notifyHardLimit(p: ApiPrincipal, usage: MonthlyUsage): Promise<void> {32 const kv = getKV();33 const key = `fch:alert:hard:${p.projectId}:${monthKey()}`;34 if (await kv.get(key).catch(() => "1")) return;35 await kv.set(key, "1", 40 * 86_400).catch(() => {});36 const limit = p.projectHardLimitUsd ?? p.orgHardLimitUsd ?? 0;37 getEmailService()38 .sendUsageAlert(p.ownerEmail, { projectName: p.projectName, spent: formatUsd(Math.max(usage.projectSpendUsd, usage.spendUsd)), limit: formatUsd(limit), kind: "hard" })39 .catch(() => {});40}4142/** Record an abuse signal (SSRF attempt, prohibited target…) for admin review. */43export async function recordAbuse(p: ApiPrincipal, requestId: string, kind: "ssrf_attempt" | "prohibited_target" | "rate_abuse", detail: string): Promise<void> {44 await db45 .insert(abuseEvents)46 .values({ id: newId("abuse"), organizationId: p.organizationId, projectId: p.projectId, requestId, kind, severity: kind === "ssrf_attempt" ? "medium" : "low", detail: detail.slice(0, 500) })47 .catch(() => {});48}49