/** * Service-role fetch wrapper. Server-side only (uses `server-only`). * * Provides a single entry point for server actions that need full PostgREST * access (bypassing the anon key's RLS). Centralizes the URL + header pattern * so the 67 files that previously used `svcHeaders(POSTGREST_SERVICE_KEY)` * have one source of truth. * * `apikey` header alone is sufficient for PostgREST — it accepts it for both * anon and service-role access (no `Authorization: Bearer` needed). Safe for * Vercel Edge Runtime. * * Usage: * import { svcFetch, svcHeaders } from "@/lib/svc-fetch"; * const res = await svcFetch("/rest/v1/brands?select=*"); * const data = await res.json(); * * For RPC calls that need to bypass RLS, use `api.rpc` with the service key * passed via headers — see `svcRpc(fn, args)` below. */ import "server-only"; import { PostgrestBuilder } from "@/lib/api"; import type { RowOf } from "@/lib/db-types"; const API_URL = (() => { const url = process.env.NEXT_PUBLIC_API_URL; if (!url) { throw new Error("Missing NEXT_PUBLIC_API_URL (server-side). Set it in .env.local."); } return url.replace(/\/$/, ""); })(); const SERVICE_KEY = (() => { const k = process.env.POSTGREST_SERVICE_KEY; if (!k) { throw new Error( "Missing POSTGREST_SERVICE_KEY. Set it in .env.local — it's the service-role key for the local PostgREST proxy.", ); } return k; })(); export function svcHeaders(): Record { return { apikey: SERVICE_KEY, "Content-Type": "application/json" }; } export async function svcFetch(path: string, init: RequestInit = {}): Promise { const url = path.startsWith("http") ? path : `${API_URL}${path.startsWith("/") ? "" : "/"}${path}`; const headers = { ...svcHeaders(), ...(init.headers as Record | undefined) }; return fetch(url, { ...init, headers }); } export type SvcResult = { data: T; error: null } | { data: null; error: { message: string } }; /** Service-role RPC call. Bypasses RLS, useful for admin server actions. */ export async function svcRpc( fn: string, args: Record = {}, ): Promise> { let res: Response; try { res = await svcFetch(`/rest/v1/rpc/${fn}`, { method: "POST", body: JSON.stringify(args), }); } catch (e) { return { data: null, error: { message: (e as Error).message } }; } if (!res.ok) { const text = await res.text().catch(() => ""); return { data: null, error: { message: text || res.statusText } }; } const data = (await res.json()) as TReturns; return { data, error: null }; } /** Re-export the API URL for any code that still needs to build URLs by hand. */ export const SVC_API_URL = API_URL; // ── Service-role PostgREST client ──────────────────────────────────────────── // // Same builder shape as `api` (the anon-key client in `@/lib/api`) but uses // the service-role key. Use this for server actions that need to bypass RLS // (admin user management, writing to `admin_users`, etc.). // // Usage: // import { svcApi } from "@/lib/svc-fetch"; // const { data, error } = await svcApi.from("admin_users").select("*"); // const { data, error } = await svcApi.from("admin_users").insert({...}).select().single(); class SvcPostgrestBuilder extends PostgrestBuilder { constructor(table: string) { super(table); this.getHeaders = () => svcHeaders(); } } export const svcApi = { from(table: TTable): SvcPostgrestBuilder> { return new SvcPostgrestBuilder>(table); }, rpc: svcRpc, };