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.3 KB · 53 lines typescript
Raw Blame History
1import { PLAN_LIMITS } from "@fetcha/core";2import { db, fetchRequests, sql } from "@fetcha/db";3import type { ApiPrincipal } from "../auth";4import { monthlyUsage } from "../limits";56export function meResponse(p: ApiPrincipal) {7  return {8    project: { id: p.projectId, name: p.projectName },9    organization: { id: p.organizationId, name: p.organizationName, plan: p.plan },10    key: { id: p.keyId, name: p.keyName, mode: p.mode, scopes: p.scopes },11  };12}1314export async function usageResponse(p: ApiPrincipal) {15  const usage = await monthlyUsage(p);16  const limits = PLAN_LIMITS[p.plan];17  const start = new Date();18  start.setUTCDate(1);19  start.setUTCHours(0, 0, 0, 0);20  const [agg] = await db21    .select({22      total: sql<number>`count(*)::int`,23      ok: sql<number>`count(*) filter (where ${fetchRequests.status} = 'success')::int`,24      bytes: sql<number>`coalesce(sum(${fetchRequests.bytesIn} + ${fetchRequests.bytesOut}), 0)::float`,25      p50: sql<number>`coalesce(percentile_cont(0.5) within group (order by ${fetchRequests.latencyMs}), 0)::float`,26      p95: sql<number>`coalesce(percentile_cont(0.95) within group (order by ${fetchRequests.latencyMs}), 0)::float`,27    })28    .from(fetchRequests)29    .where(sql`${fetchRequests.projectId} = ${p.projectId} and ${fetchRequests.createdAt} >= ${start}`);30  return {31    period_start: start.toISOString(),32    plan: { id: p.plan, label: limits.label, monthly_requests: limits.monthly_requests, concurrency: limits.concurrency },33    organization: { requests: usage.requests, spend_usd: round(usage.spendUsd) },34    project: {35      requests: usage.projectRequests,36      spend_usd: round(usage.projectSpendUsd),37      successful_requests: agg?.ok ?? 0,38      success_rate: agg?.total ? round((agg.ok / agg.total) * 100, 1) : null,39      bandwidth_bytes: agg?.bytes ?? 0,40      latency_p50_ms: Math.round(agg?.p50 ?? 0),41      latency_p95_ms: Math.round(agg?.p95 ?? 0),42    },43    remaining_requests: limits.monthly_requests >= Number.MAX_SAFE_INTEGER ? null : Math.max(0, limits.monthly_requests - usage.requests),44    browser: { enabled: true, concurrency: limits.browser_concurrency },45    crawl: { max_pages: limits.crawl_max_pages, concurrent_jobs: limits.crawl_concurrent_jobs },46  };47}4849function round(n: number, d = 4): number {50  const f = 10 ** d;51  return Math.round(n * f) / f;52}53