Party Planner Event Access
/party/:eventId (dashboard layout) and /party/:eventId/palette (editor, deliberately outside that
layout) gate the same event. Both call the same two helpers in app/lib/party/access.server.ts, in
the same order.
What each function does
| Function | Job |
|---|---|
requireValidEvent(eventId, ctx, cacheStrategy?) | Loads the event or 404s. Pass CacheStrategy.none where the caller must read its own writes. |
requireEventAccess(event, ctx, { returnTo }) | Resolves who is viewing and what they may do, or redirects. Returns { shopifyGid, permissionLevel, isDebugModeEnabled }. |
resolveDebugGid(session, event) | Debug mode only — views the event as the impersonated member, else the owner. Clears an impersonation whose GID left the roster. |
requireCustomerGid(customerAccount, session, returnTo) | Checks isLoggedIn, then reads getCustomerGid. Redirects to login if either fails. |
getPermissionLevel(gid, event) | Returns OWNER / ADMIN / MEMBER / NONE. The only membership check. |
isEventAdmin(level) | True for OWNER and ADMIN. |
api.party.debug.ts owns setImpersonation. Loaders read it or clear it but never select, so a
prefetch="intent" hover cannot change who you are viewing as.
The gate
getPermissionLevelanswers membership, so no route runs a separate roster check. It runs ahead of the Admin API enrichment fan-out, so a non-member 404s without triggering it.requireEventAccesstakes aValidEvent. That signature puts the 404 before authentication and a caller cannot reorder the two.requireEventAccessredirects onNONEand returnsExclude<EventPermissionLevel, 'NONE'>, so callers cannot forget the check.requireEventAdminasks only the admin question.- Debug mode derives the level from the impersonated GID, so an impersonated member hits exactly the denials a real member hits. It grants access without authenticating at all — a deliberate bypass for testing.
Outcomes — palette editor
| Visitor | Debug | Level | Outcome |
|---|---|---|---|
| logged out | off | — | 302 /login?return_to=…; never calls getCustomerGid |
| logged in, member | off | MEMBER | 302 /party/:eventId |
| logged in, owner/admin | off | OWNER / ADMIN | 200 editor |
| logged in or out | on, nothing impersonated | OWNER | 200 editor |
| logged in or out | on, impersonating owner/admin | OWNER / ADMIN | 200 editor |
| logged in or out | on, impersonating member | MEMBER | 302 /party/:eventId |
| logged in or out | on, impersonating a GID off the roster | OWNER | clears the impersonation, 200 editor |
Debug mode ignores login state — it takes the resolveDebugGid branch, so requireCustomerGid never
runs and the visitor's own identity has no effect on the outcome.