TypeScript 97.5%
SQL 1.4%
Python 0.8%
1/**2 * Request-log filters shared by the Requests page, its pagination links and the CSV export route.3 * Pure helpers: no database or framework imports so they can be used anywhere.4 */56export type SearchParams = Record<string, string | string[] | undefined>;78export const REQUEST_RANGES = ["24h", "7d", "30d", "custom"] as const;9export type RequestRange = (typeof REQUEST_RANGES)[number];1011export const REQUEST_STATUSES = ["success", "failed", "pending"] as const;12export type RequestStatus = (typeof REQUEST_STATUSES)[number];1314export const REQUEST_SOURCES = ["api", "playground", "sdk"] as const;15export type RequestSource = (typeof REQUEST_SOURCES)[number];1617export const REQUEST_NETWORKS = ["datacenter", "residential", "isp", "mobile"] as const;1819export const REQUESTS_PAGE_SIZE = 50;20export const REQUESTS_EXPORT_MAX = 10_000;2122export interface RequestFilters {23 range: RequestRange;24 /** ISO date (YYYY-MM-DD), only used when range = custom. */25 from?: string;26 to?: string;27 status?: RequestStatus;28 domain?: string;29 network?: (typeof REQUEST_NETWORKS)[number];30 country?: string;31 httpStatus?: number;32 requestId?: string;33 source?: RequestSource;34 page: number;35}3637function first(v: string | string[] | undefined): string | undefined {38 const s = Array.isArray(v) ? v[0] : v;39 const t = s?.trim();40 return t ? t : undefined;41}4243function oneOf<T extends readonly string[]>(v: string | undefined, allowed: T): T[number] | undefined {44 return v && (allowed as readonly string[]).includes(v) ? (v as T[number]) : undefined;45}4647const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;4849export function parseRequestFilters(sp: SearchParams): RequestFilters {50 const from = first(sp.from);51 const to = first(sp.to);52 const hasCustomDates = Boolean((from && ISO_DATE.test(from)) || (to && ISO_DATE.test(to)));53 const rangeRaw = oneOf(first(sp.range), REQUEST_RANGES);54 const range: RequestRange = hasCustomDates && (!rangeRaw || rangeRaw === "custom") ? "custom" : rangeRaw && rangeRaw !== "custom" ? rangeRaw : hasCustomDates ? "custom" : "7d";55 const httpRaw = first(sp.http);56 const httpStatus = httpRaw && /^\d{3}$/.test(httpRaw) ? Number(httpRaw) : undefined;57 const pageRaw = Number(first(sp.page) ?? "1");58 const country = first(sp.country)?.toUpperCase();59 return {60 range,61 from: range === "custom" && from && ISO_DATE.test(from) ? from : undefined,62 to: range === "custom" && to && ISO_DATE.test(to) ? to : undefined,63 status: oneOf(first(sp.status), REQUEST_STATUSES),64 domain: first(sp.domain)?.toLowerCase().slice(0, 253),65 network: oneOf(first(sp.network), REQUEST_NETWORKS),66 country: country && /^[A-Z]{2}$/.test(country) ? country : undefined,67 httpStatus,68 requestId: first(sp.id)?.slice(0, 64),69 source: oneOf(first(sp.source), REQUEST_SOURCES),70 page: Number.isFinite(pageRaw) && pageRaw >= 1 ? Math.floor(pageRaw) : 1,71 };72}7374/** Absolute time window for a filter set. `to` is exclusive; null means "now". */75export function filterWindow(f: RequestFilters, now = new Date()): { from: Date; to: Date | null } {76 if (f.range === "custom") {77 const from = f.from ? new Date(`${f.from}T00:00:00.000Z`) : new Date(now.getTime() - 30 * 86_400_000);78 const to = f.to ? new Date(new Date(`${f.to}T00:00:00.000Z`).getTime() + 86_400_000) : null;79 return { from, to };80 }81 const hours = f.range === "24h" ? 24 : f.range === "7d" ? 24 * 7 : 24 * 30;82 return { from: new Date(now.getTime() - hours * 3_600_000), to: null };83}8485/** Serialize filters back to a query string. `overrides` lets callers change a page or drop a key (pass undefined). */86export function filtersToSearchParams(f: RequestFilters, overrides: Partial<Record<keyof RequestFilters, string | number | undefined>> = {}): URLSearchParams {87 const merged: Record<string, string | number | undefined> = {88 range: f.range,89 from: f.from,90 to: f.to,91 status: f.status,92 domain: f.domain,93 network: f.network,94 country: f.country,95 http: f.httpStatus,96 id: f.requestId,97 source: f.source,98 page: f.page > 1 ? f.page : undefined,99 };100 for (const [k, v] of Object.entries(overrides)) {101 const key = k === "httpStatus" ? "http" : k === "requestId" ? "id" : k;102 merged[key] = v;103 }104 if (merged.range === "7d" && !merged.from && !merged.to) delete merged.range;105 if (merged.range !== "custom") {106 delete merged.from;107 delete merged.to;108 }109 const sp = new URLSearchParams();110 for (const [k, v] of Object.entries(merged)) if (v !== undefined && v !== "" && v !== null) sp.set(k, String(v));111 return sp;112}113114export function requestsHref(f: RequestFilters, overrides?: Partial<Record<keyof RequestFilters, string | number | undefined>>): string {115 const qs = filtersToSearchParams(f, overrides).toString();116 return `/dashboard/requests${qs ? `?${qs}` : ""}`;117}118119export interface FilterChip {120 key: keyof RequestFilters;121 label: string;122 value: string;123}124125export function activeFilterChips(f: RequestFilters): FilterChip[] {126 const chips: FilterChip[] = [];127 if (f.range === "custom") chips.push({ key: "range", label: "Dates", value: `${f.from ?? "…"} → ${f.to ?? "now"}` });128 else if (f.range !== "7d") chips.push({ key: "range", label: "Range", value: f.range === "24h" ? "Last 24 hours" : "Last 30 days" });129 if (f.status) chips.push({ key: "status", label: "Status", value: f.status });130 if (f.domain) chips.push({ key: "domain", label: "Domain", value: f.domain });131 if (f.network) chips.push({ key: "network", label: "Network", value: f.network });132 if (f.country) chips.push({ key: "country", label: "Country", value: f.country });133 if (f.httpStatus) chips.push({ key: "httpStatus", label: "HTTP", value: String(f.httpStatus) });134 if (f.requestId) chips.push({ key: "requestId", label: "Request", value: f.requestId });135 if (f.source) chips.push({ key: "source", label: "Source", value: f.source });136 return chips;137}138139/** Remove one chip: custom ranges drop both dates and fall back to the default window. */140export function hrefWithoutChip(f: RequestFilters, key: keyof RequestFilters): string {141 if (key === "range") return requestsHref({ ...f, range: "7d", from: undefined, to: undefined, page: 1 });142 return requestsHref({ ...f, [key]: undefined, page: 1 });143}144