fix: Multiple Admin Support#46
Conversation
|
@SyedHannanMehdi is attempting to deploy a commit to the fahad-dezloper's projects Team on Vercel. A member of the Team first needs to authorize it. |
There was a problem hiding this comment.
Pull request overview
Adds an admin-role system intended to support multiple admins by introducing a role enum, invite flow, and admin-only API endpoints.
Changes:
- Introduces
AdminRole,User.role, and anAdminInvitemodel + migration. - Adds admin helper utilities (
getCurrentUser, role hierarchy checks) and admin API routes to list users, update roles, and issue/accept invites. - Adds a new NextAuth configuration (
authOptions) and NextAuth type augmentation forsession.user.role.
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| types/next-auth.d.ts | Extends NextAuth session/user types to include id and role. |
| prisma/schema.prisma | Replaces Prisma schema with AdminRole + invite + (unrelated) new auth/campaign models. |
| prisma/migrations/20240101000000_add_admin_roles/migration.sql | Adds AdminRole enum, User.role, and AdminInvite table. |
| lib/auth.ts | Adds NextAuth authOptions with PrismaAdapter and a session callback adding role/id. |
| lib/admin.ts | Adds helpers for current user lookup, role checks, and seeding a super admin. |
| app/api/admin/users/route.ts | Adds admin-protected endpoint to list users (and roles). |
| app/api/admin/users/[id]/role/route.ts | Adds super-admin-only role update/removal endpoints. |
| app/api/admin/invite/route.ts | Adds super-admin invite creation and pending-invite listing endpoints. |
| app/api/admin/invite/accept/route.ts | Adds endpoint for an authenticated user to accept an invite token. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const updated = await prisma.user.update({ | ||
| where: { id }, | ||
| data: { role: null }, | ||
| select: { id: true, name: true, email: true, role: true }, | ||
| }); |
There was a problem hiding this comment.
Same as PATCH: prisma.user.update will throw if id doesn’t exist, causing an unhandled 500. Consider handling the “user not found” case and returning 404 for invalid ids.
| // GET /api/admin/users — list all users with roles (ADMIN+) | ||
| export async function GET() { | ||
| const user = await getCurrentUser(); | ||
|
|
||
| if (!user || !hasRole(user.role, AdminRole.ADMIN)) { | ||
| return NextResponse.json({ error: "Forbidden" }, { status: 403 }); | ||
| } | ||
|
|
||
| const users = await prisma.user.findMany({ |
There was a problem hiding this comment.
The comment says this endpoint lists “all users with roles”, but the query returns all users (including those with role: null). Either update the comment to match behavior or filter the query (e.g., where: { role: { not: null } }) if the endpoint should only return admins.
| enum AdminRole { | ||
| SUPER_ADMIN | ||
| ADMIN | ||
| MODERATOR | ||
| } | ||
|
|
||
| model Room { | ||
| id String @id @default(uuid()) | ||
| code String @unique | ||
| name String | ||
| adminId String | ||
| admin User @relation(fields: [adminId], references: [id]) | ||
| users RoomUser[] | ||
| streams Stream[] | ||
| messages Message[] | ||
| allowSongAdd Boolean @default(true) // Admin can control this | ||
| createdAt DateTime @default(now()) | ||
| model User { | ||
| id String @id @default(cuid()) | ||
| name String? | ||
| email String? @unique | ||
| emailVerified DateTime? | ||
| image String? | ||
| role AdminRole? | ||
| accounts Account[] | ||
| sessions Session[] | ||
| campaigns Campaign[] | ||
| donations Donation[] | ||
| adminInvitesSent AdminInvite[] @relation("InvitedBy") | ||
|
|
||
| createdAt DateTime @default(now()) | ||
| updatedAt DateTime @updatedAt | ||
| } |
There was a problem hiding this comment.
prisma/schema.prisma has been rewritten to a completely different domain model (Campaign/Donation + NextAuth Account/Session tables) and removes existing models (e.g., Room/RoomUser/Stream/Upvote/Message) that are referenced throughout the app (e.g., prismaClient.room in app/api/room/create). This will break Prisma Client generation and runtime queries, and it also no longer matches the existing migration history. Suggestion: revert to the current schema and apply the “multiple admin support” change incrementally (e.g., add AdminRole + User.role + AdminInvite while keeping the existing Room/Stream models intact).
| -- AlterTable: Add role to User | ||
| ALTER TABLE "User" ADD COLUMN "role" "AdminRole"; | ||
|
|
There was a problem hiding this comment.
The migration folder name 20240101000000_add_admin_roles sorts before the existing 202502..._init migration, but it runs ALTER TABLE "User" ... which will fail if User hasn’t been created yet. Rename/regenerate this migration with a timestamp after the latest migration and ensure it is based on the current schema state so it applies cleanly.
| import { NextAuthOptions } from "next-auth"; | ||
| import GoogleProvider from "next-auth/providers/google"; | ||
| import { PrismaAdapter } from "@auth/prisma-adapter"; | ||
| import { prisma } from "@/lib/prisma"; | ||
|
|
||
| export const authOptions: NextAuthOptions = { | ||
| // @ts-ignore – PrismaAdapter type mismatch between next-auth versions | ||
| adapter: PrismaAdapter(prisma), |
There was a problem hiding this comment.
lib/auth.ts won’t compile as-is: it imports PrismaAdapter from @auth/prisma-adapter, but that package isn’t listed in package.json, and it also imports prisma from @/lib/prisma which doesn’t exist in the repo. Either add the missing dependency and a lib/prisma.ts singleton (or switch these imports to the existing prismaClient from app/lib/db.ts) so the auth configuration can actually be used.
| import { getServerSession } from "next-auth"; | ||
| import { authOptions } from "@/lib/auth"; | ||
| import { prisma } from "@/lib/prisma"; | ||
| import { AdminRole } from "@prisma/client"; | ||
|
|
||
| /** | ||
| * Returns the current session's user with their role, | ||
| * or null if the user is not authenticated. | ||
| */ | ||
| export async function getCurrentUser() { | ||
| const session = await getServerSession(authOptions); | ||
| if (!session?.user?.email) return null; |
There was a problem hiding this comment.
getServerSession(authOptions) here will only work if the same authOptions is also used by the NextAuth handler in app/api/auth/[...nextauth]/route.ts. Currently the repo defines NextAuth options inline in that route (no adapter / different callbacks), so this can lead to sessions not being decoded or user data not matching. Consider centralizing the NextAuth config (export authOptions and use it both in the handler and in getServerSession).
| const updated = await prisma.user.update({ | ||
| where: { id }, | ||
| data: { role: role ?? null }, | ||
| select: { id: true, name: true, email: true, role: true }, | ||
| }); |
There was a problem hiding this comment.
prisma.user.update({ where: { id } ... }) will throw a Prisma P2025 error if the user id doesn’t exist, which will currently surface as a 500 from the route. Handle the “record not found” case explicitly (e.g., catch Prisma known request errors and return 404).
…y add AdminRole enum, User.role, and AdminInvite incrementally
…2502..._init migration and runs after User table is created
…01000000_add_admin_roles which has correct ordering
Automated fix for #27 — implemented by CashClaw agent.
Closes #27