d892b3f64f
Deploy to route.crispygoat.com / deploy (push) Failing after 9s
Phase 1 — Pattern library - Add src/lib/api.ts (typed PostgREST client, anon-key) - Add src/lib/svc-fetch.ts (service-role fetch + PostgREST client) - Add src/lib/db-types.ts (Database generic, RowOf helper) - Add src/lib/auth-admin.ts (better-auth admin wrappers: createUser, listUsers, removeUser, requestPasswordReset, validateSession) Phase 2 — Server action migration - Migrate all 67 server actions off @supabase/supabase-js to svcApi/svcRpc - Replace service.auth.admin.* with authAdmin.* (better-auth) - Replace publicApi.auth.* with authAdmin.* (better-auth) - Add typed single() null guards + nullable column fallbacks Phase 3 — Delete mock data + debug routes - Remove if(useMockData) branches from 7 files (-226 lines) - Delete src/lib/mock-data.ts (no longer referenced) - Delete 7 debug API routes (debug-admin-users, debug-auth, debug-cookie, debug-env, debug-get-admin-users, debug-hello, debug-me) Phase 4 — Hard cut - Delete src/lib/supabase.ts (orphan — no importers) - Delete src/app/api/supabase + src/app/api/supabase-test routes - Remove @supabase/ssr + @supabase/supabase-js from package.json - Rename env vars: NEXT_PUBLIC_SUPABASE_URL → NEXT_PUBLIC_API_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY → NEXT_PUBLIC_API_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY → POSTGREST_SERVICE_KEY Phase 5 — Verify - npx tsc --noEmit: 0 errors - npm run lint: 0 new errors from migration (104 pre-existing errors unrelated) - npm run build: same failure mode as main worktree (PostgREST-dependent prerender), no regression Net: 158 files changed, +1640 / -1903 lines (-263 net)
105 lines
3.7 KiB
TypeScript
105 lines
3.7 KiB
TypeScript
/**
|
|
* 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<T>(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<string, string> {
|
|
return { apikey: SERVICE_KEY, "Content-Type": "application/json" };
|
|
}
|
|
|
|
export async function svcFetch(path: string, init: RequestInit = {}): Promise<Response> {
|
|
const url = path.startsWith("http") ? path : `${API_URL}${path.startsWith("/") ? "" : "/"}${path}`;
|
|
const headers = { ...svcHeaders(), ...(init.headers as Record<string, string> | undefined) };
|
|
return fetch(url, { ...init, headers });
|
|
}
|
|
|
|
export type SvcResult<T> = { data: T; error: null } | { data: null; error: { message: string } };
|
|
|
|
/** Service-role RPC call. Bypasses RLS, useful for admin server actions. */
|
|
export async function svcRpc<TReturns = unknown>(
|
|
fn: string,
|
|
args: Record<string, unknown> = {},
|
|
): Promise<SvcResult<TReturns>> {
|
|
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<TRow, TResult = TRow[]> extends PostgrestBuilder<TRow, TResult> {
|
|
constructor(table: string) {
|
|
super(table);
|
|
this.getHeaders = () => svcHeaders();
|
|
}
|
|
}
|
|
|
|
export const svcApi = {
|
|
from<TTable extends string>(table: TTable): SvcPostgrestBuilder<RowOf<TTable>> {
|
|
return new SvcPostgrestBuilder<RowOf<TTable>>(table);
|
|
},
|
|
rpc: svcRpc,
|
|
};
|