import { cookies } from "next/headers"; import PocketBase from "pocketbase"; export const PB_URL = process.env.POCKETBASE_URL || "http://pocketbase-zinlbpsext2ro0lgb2558vfy.169.239.180.130.sslip.io"; /** * Plain fetch helper for RSC — avoids the PB SDK's auto-cancel wrapper. */ async function pbFetch(path: string, token?: string, init?: RequestInit) { const res = await fetch(`${PB_URL}${path}`, { ...init, headers: { "Content-Type": "application/json", ...(token ? { Authorization: token } : {}), ...(init?.headers || {}), }, cache: "no-store", }); if (!res.ok) { const text = await res.text(); throw new Error(`PB ${init?.method || "GET"} ${path} failed: ${res.status} ${text}`); } return res.json(); } /** * Get a PB client pre-loaded with the user's auth token from the cookie. * Use this for actions/route handlers; for RSC use pbFetch() directly. */ export async function getServerPB() { const cookieStore = await cookies(); const cookie = cookieStore.get("pb_auth"); const pb = new PocketBase(PB_URL); if (cookie?.value) { pb.authStore.save(cookie.value, null); } return pb; } /** * Browser-side PB (realtime subscriptions, etc.) */ export function getBrowserPB() { return new PocketBase(PB_URL); } /** * PB record list response shape (subset we use). */ export interface PBList { items: T[]; page: number; perPage: number; totalItems: number; totalPages: number; } /** * Server-side PB helpers using plain fetch (RSC-safe). */ export async function pbList( collection: string, opts: { page?: number; perPage?: number; filter?: string; sort?: string; fields?: string; expand?: string } = {}, token?: string, ): Promise> { const params = new URLSearchParams(); if (opts.page) params.set("page", String(opts.page)); if (opts.perPage) params.set("perPage", String(opts.perPage)); if (opts.filter) params.set("filter", opts.filter); if (opts.sort) params.set("sort", opts.sort); if (opts.fields) params.set("fields", opts.fields); if (opts.expand) params.set("expand", opts.expand); return pbFetch(`/api/collections/${collection}/records?${params}`, token); } /** * Auto-paginated version of pbList. PB caps perPage at 1000 server-side, * so for collections with >1000 records this is the only way to get them all. * Loops server-side pagination until every page is consumed. */ export async function pbListAll( collection: string, opts: { filter?: string; sort?: string; fields?: string; expand?: string; perPage?: number } = {}, token?: string, ): Promise { const perPage = opts.perPage ?? 1000; const out: T[] = []; let page = 1; // Safety cap at 50 pages (50,000 records) — adjust if you have more for (let i = 0; i < 50; i++) { const res = await pbList(collection, { ...opts, page, perPage }, token); out.push(...res.items); if (page >= res.totalPages) break; page++; } return out; } export async function pbGet(collection: string, id: string, expand?: string, token?: string): Promise { const params = new URLSearchParams(); if (expand) params.set("expand", expand); return pbFetch(`/api/collections/${collection}/records/${id}?${params}`, token); } export async function pbCreate(collection: string, body: any, token?: string): Promise { return pbFetch(`/api/collections/${collection}/records`, token, { method: "POST", body: JSON.stringify(body), }); } export async function pbUpdate(collection: string, id: string, body: any, token?: string): Promise { return pbFetch(`/api/collections/${collection}/records/${id}`, token, { method: "PATCH", body: JSON.stringify(body), }); } export async function pbDelete(collection: string, id: string, token?: string): Promise { await pbFetch(`/api/collections/${collection}/records/${id}`, token, { method: "DELETE" }); }