import { redirect } from 'next/navigation'; import { ownerRoleAtLeast, OwnerRoleSchema, type OwnerRole } from '@nexumi/shared'; import { ForbiddenError, UnauthorizedError, requireAuth, requireAuthOrRedirect } from './auth'; import { env } from './env'; import { prisma } from './prisma'; import type { SessionPayload } from './session'; export type OwnerSession = SessionPayload & { ownerRole: OwnerRole }; function bootstrapOwnerIds(): string[] { return env.OWNER_USER_IDS ?? []; } export async function resolveOwnerRole(userId: string): Promise { if (bootstrapOwnerIds().includes(userId)) { return 'OWNER'; } const member = await prisma.ownerTeamMember.findUnique({ where: { userId } }); if (!member) { return null; } const parsed = OwnerRoleSchema.safeParse(member.role); return parsed.success ? parsed.data : null; } export async function isOwnerUser(userId: string): Promise { return (await resolveOwnerRole(userId)) !== null; } /** * Owner-panel team members with SUPPORT+ may open every guild where the bot is * installed — for support without needing Manage Guild on Discord. * VIEWER is owner-panel read-only and does not bypass guild dashboard APIs. */ export async function hasOwnerGuildBypass(userId: string): Promise { const role = await resolveOwnerRole(userId); if (!role) { return false; } return ownerRoleAtLeast(role, 'SUPPORT'); } export async function requireOwner(minimum: OwnerRole = 'VIEWER'): Promise { const session = await requireAuth(); const role = await resolveOwnerRole(session.user.id); if (!role) { throw new ForbiddenError('Owner panel access required'); } if (!ownerRoleAtLeast(role, minimum)) { throw new ForbiddenError('Insufficient owner role'); } return { ...session, ownerRole: role }; } export async function requireOwnerOrRedirect(minimum: OwnerRole = 'VIEWER'): Promise { const session = await requireAuthOrRedirect(); const role = await resolveOwnerRole(session.user.id); if (!role || !ownerRoleAtLeast(role, minimum)) { redirect('/dashboard'); } return { ...session, ownerRole: role }; } export { UnauthorizedError, ForbiddenError };