fix command-center RPCs against prod schema and setof call
Deploy to route.crispygoat.com / deploy (push) Successful in 4m17s
Deploy to route.crispygoat.com / deploy (push) Successful in 4m17s
Migration 0040 was written against an assumed schema and referenced
columns that don't exist in the prod orders table:
- orders.created_at → use orders.placed_at
- orders.subtotal → use orders.total_cents / 100
- stops.date regex → column is already DATE, not TEXT, drop the
~ '^\d{4}-...$' check
Migration 0041 fixes both broken functions in place via
CREATE OR REPLACE FUNCTION.
The action layer at src/actions/platform/command-center.ts used
SELECT fn() AS "fn" for all platform RPCs, but two of the three
return TABLE(...) (setof) — the correct syntax is SELECT * FROM fn().
The platformRPC<T> helper now takes a kind: 'scalar' | 'setof' flag
and emits the right SQL.
Verified on prod:
METRICS: { orders_today:0, active_brands:1, active_routes:538, ... }
BRAND_HEALTH: 1 row (Tuxedo Corn), 538 active_stops, 0 failed, healthy schema
ACTIVITY: 0 rows (operational_events has no brand_id in payload, no error)
This commit is contained in:
@@ -64,34 +64,50 @@ export type CreatePainLogData = {
|
||||
|
||||
// ── Internal RPC helper ──────────────────────────────────────────────────────
|
||||
|
||||
async function platformRPC<T>(rpcName: string): Promise<T> {
|
||||
/**
|
||||
* Call a platform RPC. The action layer needs to know whether the function
|
||||
* returns a scalar (JSONB) or a setof (TABLE(...)) — `SELECT fn() AS "fn"`
|
||||
* only works for scalar returns; setof functions need `SELECT * FROM fn()`.
|
||||
*
|
||||
* - `kind: "scalar"` → wraps the single result column in a JSON value
|
||||
* - `kind: "setof"` → returns the rows directly as an array
|
||||
*/
|
||||
async function platformRPC<T>(
|
||||
rpcName: string,
|
||||
kind: "scalar" | "setof" = "scalar",
|
||||
): Promise<T> {
|
||||
const adminUser = await getAdminUser();
|
||||
if (!adminUser) throw new Error("Not authenticated");
|
||||
if (adminUser.role !== "platform_admin") throw new Error("Access denied: platform admin only");
|
||||
|
||||
if (kind === "setof") {
|
||||
const { rows } = await pool.query(
|
||||
`SELECT * FROM ${rpcName}()`,
|
||||
);
|
||||
return rows as unknown as T;
|
||||
}
|
||||
|
||||
// scalar: e.g. JSONB return — single cell, possibly null
|
||||
const { rows } = await pool.query<Record<string, T>>(
|
||||
`SELECT ${rpcName}() AS "${rpcName}"`,
|
||||
);
|
||||
|
||||
const data = rows[0]?.[rpcName];
|
||||
if (data == null) {
|
||||
return null as T;
|
||||
}
|
||||
if (data == null) return null as T;
|
||||
return data as T;
|
||||
}
|
||||
|
||||
// ── Platform actions ─────────────────────────────────────────────────────────
|
||||
|
||||
export async function getPlatformMetrics(): Promise<PlatformMetrics> {
|
||||
return platformRPC<PlatformMetrics>("get_platform_command_center_metrics");
|
||||
return platformRPC<PlatformMetrics>("get_platform_command_center_metrics", "scalar");
|
||||
}
|
||||
|
||||
export async function getPlatformActivityFeed(): Promise<ActivityEvent[]> {
|
||||
return platformRPC<ActivityEvent[]>("get_platform_activity_feed");
|
||||
return platformRPC<ActivityEvent[]>("get_platform_activity_feed", "setof");
|
||||
}
|
||||
|
||||
export async function getBrandHealthSnapshot(): Promise<BrandHealth[]> {
|
||||
return platformRPC<BrandHealth[]>("get_brand_health_snapshot");
|
||||
return platformRPC<BrandHealth[]>("get_brand_health_snapshot", "setof");
|
||||
}
|
||||
|
||||
export async function getPainLog(): Promise<PainLogItem[]> {
|
||||
|
||||
Reference in New Issue
Block a user