feat: moderator-scoped admin access + fix push notifications not displaying (v0.66.0)
Moderator role existed in the schema and was already respected by
comment deletion, but every admin page/route treated moderator
identically to a regular user (403/redirect). Wires it up narrowly:
admin/layout.tsx now lets admin+moderator through and filters the
nav by role, while every admin-only page (users, tiers, settings,
webhooks, insights, etc.) explicitly redirects moderators away via a
new requireFullAdminPage() helper -- the nav filter is UX, this is
the actual gate. Moderators land on Reports and Recipes: reports
GET/PATCH now accept requireAdmin({allowModerator: true}), and a new
PATCH /api/v1/admin/recipes/[id] lets admin+moderator unpublish a
public recipe (flip to private) as a takedown action, audit-logged.
Also found and fixed a real bug while auditing the PWA push pipeline
for a "push click-through" gap: public/sw.js had no `push` event
listener at all, so incoming push messages never displayed anything
-- push was silently non-functional end-to-end despite the
subscribe/send plumbing all working. Added the push listener
(showNotification) and a notificationclick listener that focuses an
existing tab or opens one at the payload's url.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
import { redirect } from "next/navigation";
|
||||
import { headers } from "next/headers";
|
||||
import { auth } from "@/lib/auth/server";
|
||||
import { db, users, eq } from "@epicure/db";
|
||||
|
||||
export type StaffRole = "admin" | "moderator";
|
||||
|
||||
/** Server-component equivalent of requireAdmin/lib/api-auth.ts — used by
|
||||
* admin/layout.tsx to gate entry to the whole /admin tree (admin AND
|
||||
* moderator both pass) and by individual page components that need to
|
||||
* additionally restrict themselves to admin only. Always re-queries the
|
||||
* role fresh, same reasoning as requireAdmin: session.user.role comes from
|
||||
* a 5-minute cookieCache. */
|
||||
export async function getStaffRole(): Promise<StaffRole | null> {
|
||||
const session = await auth.api.getSession({ headers: await headers() });
|
||||
if (!session) return null;
|
||||
const [dbUser] = await db.select({ role: users.role }).from(users).where(eq(users.id, session.user.id)).limit(1);
|
||||
if (dbUser?.role === "admin" || dbUser?.role === "moderator") return dbUser.role;
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Redirects moderators to /admin/reports (their only allowed landing area)
|
||||
* and non-staff to /recipes. Call at the top of any admin page that should
|
||||
* stay admin-only. */
|
||||
export async function requireFullAdminPage(): Promise<void> {
|
||||
const role = await getStaffRole();
|
||||
if (role === "moderator") redirect("/admin/reports");
|
||||
if (role !== "admin") redirect("/recipes");
|
||||
}
|
||||
Reference in New Issue
Block a user