Files
zooqueen 5dcb3292dc fix(rpc): dial the ZAP socket at the page origin
A WebSocket is dialled by absolute origin and has no relative form, but
getBaseUrl() answers '' in the browser — the convention the HTTP transport
wants — so zapUrl() built `new URL('')` and threw before the socket opened.
Read the origin off the page there; keep the configured URL off-page.

isAdmin answers false for an absent user or roles column instead of
throwing, so the app shell renders while a session refresh settles.

closeZap claims the connection slot before awaiting, so a dial that races a
logout gets a fresh connection instead of an orphaned one.

ARCHITECTURE: the internal RPC mount is /zap over WebSocket, routed by
`<router>.<procedure>` in the body; /api/trpc no longer exists.
2026-08-13 22:44:49 -07:00

18 lines
814 B
TypeScript

import type { User } from '@prisma/client';
/**
* Whether the user holds the platform admin role.
*
* `Role` is declared in the schema but no model field references it — on the
* Base SQLite store `User.roles` is a JSON `TEXT` column — so Prisma omits the
* enum from the generated client and `Role.ADMIN` reads `undefined.ADMIN`.
* The role name is the stable value; match it directly.
*
* `roles` reaches here as a decoded `string[]` (packages/prisma/json-array.ts
* decodes it at the data layer). Callers include the app shell, which renders
* before a session refresh settles, so an absent user or column answers `false`
* rather than throwing and taking the whole tree down.
*/
export const isAdmin = (user?: Pick<User, 'roles'> | null): boolean =>
user?.roles?.includes('ADMIN') ?? false;