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%
3.1 KB · 78 lines typescript
Raw Blame History
1"use server";23import { revalidatePath } from "next/cache";4import { z } from "zod";5import { COUNTRIES } from "@fetcha/core";6import { getWorkspace } from "@/lib/session";7import { internalApi, InternalApiError } from "@/lib/api";8import type { ActionResult } from "./projects";910/** Session as returned by the Fetcha API (`POST /v1/sessions`). Provider details are never included. */11export interface DashboardSession {12  id: string;13  label: string | null;14  status: string;15  network: string;16  country: string | null;17  region: string | null;18  city: string | null;19  request_count: number;20  last_used_at: string | null;21  expires_at: string;22  created_at: string;23}2425const createSchema = z.object({26  country: z27    .string()28    .trim()29    .length(2)30    .toUpperCase()31    .refine((c) => c in COUNTRIES, "Unsupported country")32    .optional(),33  region: z.string().trim().max(64).optional(),34  city: z.string().trim().max(128).optional(),35  // Only `auto` and `residential` are live in V1; the API rejects the others with NETWORK_UNAVAILABLE.36  network: z.enum(["auto", "residential", "datacenter", "isp", "mobile"]).default("auto"),37  ttl: z.coerce.number().int().min(60).max(1800).default(600),38  label: z.string().trim().max(128).optional(),39});4041export type CreateSessionInput = z.input<typeof createSchema>;4243function clean<T extends Record<string, unknown>>(o: T): Partial<T> {44  return Object.fromEntries(Object.entries(o).filter(([, v]) => v !== undefined && v !== "")) as Partial<T>;45}4647export async function createDashboardSession(input: CreateSessionInput): Promise<ActionResult<DashboardSession>> {48  const ws = await getWorkspace();49  const parsed = createSchema.safeParse(clean(input as Record<string, unknown>));50  if (!parsed.success) return { ok: false, error: parsed.error.issues[0]?.message ?? "Invalid input", field: String(parsed.error.issues[0]?.path[0] ?? "") };51  try {52    const session = (await internalApi.createSession(ws.project.id, ws.user.id, clean(parsed.data))) as DashboardSession;53    revalidatePath("/dashboard/sessions");54    revalidatePath("/dashboard");55    return { ok: true, data: session };56  } catch (e) {57    if (e instanceof InternalApiError) return { ok: false, error: e.code === "NETWORK_UNAVAILABLE" ? "That network class is not available yet. Use auto or residential." : e.message };58    return { ok: false, error: "Could not create the session. Please try again." };59  }60}6162export async function closeDashboardSession(id: string): Promise<ActionResult> {63  const ws = await getWorkspace();64  if (!/^sess_[A-Za-z0-9]{4,64}$/.test(id)) return { ok: false, error: "Invalid session id" };65  try {66    await internalApi.closeSession(ws.project.id, ws.user.id, id);67    revalidatePath("/dashboard/sessions");68    revalidatePath("/dashboard");69    return { ok: true };70  } catch (e) {71    if (e instanceof InternalApiError) {72      if (e.code === "SESSION_NOT_FOUND") return { ok: false, error: "Session not found in this project." };73      return { ok: false, error: e.message };74    }75    return { ok: false, error: "Could not close the session. Please try again." };76  }77}78