SPB Git forge

spb/fetcha

Public
11commits 1branches 0releases
1.5 MBsize
maindefault branch
16 days agolast push
TypeScript 97.5% SQL 1.4% Python 0.8%
2.5 KB · 49 lines typescript
Raw Blame History
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