// lib/permission/servicee.ts "use server"; import db from "@/db/db"; import { eq, and, inArray, or, ilike, sql } from "drizzle-orm"; import { permissions, rolePermissions, userPermissions, permissionAuditLogs, userRoles, menuAssignments, menuRequiredPermissions, users, vendors, roles, } from "@/db/schema"; import { getServerSession } from "next-auth/next" import { authOptions } from "@/app/api/auth/[...nextauth]/route" // 역할에 권한 할당 export async function assignPermissionsToRole( roleId: number, permissionIds: number[] ) { const session = await getServerSession(authOptions) if (!session?.user?.id) { throw new Error("인증이 필요합니다.") } const currentUserId = Number(session.user.id) // 권한 체크 if (!await checkUserPermission(currentUserId, "admin.permissions.manage")) { throw new Error("권한 관리 권한이 없습니다."); } await db.transaction(async (tx) => { // 기존 권한 삭제 await tx.delete(rolePermissions) .where(eq(rolePermissions.roleId, roleId)); // 새 권한 추가 if (permissionIds.length > 0) { await tx.insert(rolePermissions).values( permissionIds.map(permissionId => ({ roleId, permissionId, grantedBy: currentUserId, })) ); // 감사 로그 await tx.insert(permissionAuditLogs).values( permissionIds.map(permissionId => ({ targetType: "role", targetId: roleId, permissionId, action: "grant", performedBy: currentUserId, reason: "역할 권한 일괄 업데이트", })) ); } }); return { success: true }; } // 역할의 권한 목록 조회 // 역할 권한 조회 (기존 함수) export async function getRolePermissions(roleId: number) { try { // 역할에 할당된 권한 조회 const assignedPermissions = await db .select({ id: permissions.id, permissionKey: permissions.permissionKey, name: permissions.name, description: permissions.description, resource: permissions.resource, action: permissions.action, permissionType: permissions.permissionType, scope: permissions.scope, menuPath: permissions.menuPath, }) .from(rolePermissions) .innerJoin(permissions, eq(permissions.id, rolePermissions.permissionId)) .where( and( eq(rolePermissions.roleId, roleId), eq(rolePermissions.isActive, true) ) ); // 모든 활성 권한 조회 const allPermissions = await db .select({ id: permissions.id, permissionKey: permissions.permissionKey, name: permissions.name, description: permissions.description, resource: permissions.resource, action: permissions.action, permissionType: permissions.permissionType, scope: permissions.scope, menuPath: permissions.menuPath, }) .from(permissions) .where(eq(permissions.isActive, true)) .orderBy(permissions.resource, permissions.name); return { permissions: allPermissions, assignedPermissionIds: assignedPermissions.map(p => p.id) }; } catch (error) { console.error('Failed to get role permissions:', error); throw new Error('역할 권한 조회에 실패했습니다.'); } } // 권한 체크 함수 export async function checkUserPermission( userId: number, permissionKey: string ): Promise { // 역할 기반 권한 const roleBasedPerms = await db .selectDistinct({ permissionKey: permissions.permissionKey }) .from(userRoles) .innerJoin(rolePermissions, eq(rolePermissions.roleId, userRoles.roleId)) .innerJoin(permissions, eq(permissions.id, rolePermissions.permissionId)) .where( and( eq(userRoles.userId, userId), eq(permissions.permissionKey, permissionKey), eq(permissions.isActive, true), eq(rolePermissions.isActive, true) ) ); if (roleBasedPerms.length > 0) return true; // 사용자 직접 권한 const directPerms = await db .selectDistinct({ permissionKey: permissions.permissionKey }) .from(userPermissions) .innerJoin(permissions, eq(permissions.id, userPermissions.permissionId)) .where( and( eq(userPermissions.userId, userId), eq(permissions.permissionKey, permissionKey), eq(permissions.isActive, true), eq(userPermissions.isActive, true), eq(userPermissions.isGrant, true) // 부여된 권한만 ) ); return directPerms.length > 0; } // 메뉴 접근 권한 체크 export async function checkMenuAccess( userId: number, menuPath: string ): Promise { // 메뉴 담당자인 경우 자동 허용 const isManager = await db .selectDistinct({ id: menuAssignments.id }) .from(menuAssignments) .where( and( eq(menuAssignments.menuPath, menuPath), or( eq(menuAssignments.manager1Id, userId), eq(menuAssignments.manager2Id, userId) ) ) ); if (isManager.length > 0) return true; // 메뉴 필수 권한 체크 const requiredPerms = await db .select({ permissionKey: permissions.permissionKey }) .from(menuRequiredPermissions) .innerJoin(permissions, eq(permissions.id, menuRequiredPermissions.permissionId)) .where( and( eq(menuRequiredPermissions.menuPath, menuPath), eq(menuRequiredPermissions.isRequired, true) ) ); if (requiredPerms.length === 0) return true; // 필수 권한이 없으면 모두 접근 가능 // 사용자가 필수 권한을 모두 가지고 있는지 확인 for (const perm of requiredPerms) { if (!await checkUserPermission(userId, perm.permissionKey)) { return false; } } return true; } export async function searchUsers(query: string) { const usersData = await db .select({ id: users.id, name: users.name, email: users.email, imageUrl: users.imageUrl, domain: users.domain, companyName: vendors.vendorName, }) .from(users) .leftJoin(vendors, eq(vendors.id, users.companyId)) .where( or( ilike(users.name, `%${query}%`), ilike(users.email, `%${query}%`) ) ) .limit(20); // 각 사용자의 역할 조회 const usersWithRoles = await Promise.all( usersData.map(async (user) => { const userRolesData = await db .select({ id: roles.id, name: roles.name, }) .from(userRoles) .innerJoin(roles, eq(roles.id, userRoles.roleId)) .where(eq(userRoles.userId, user.id)); return { ...user, roles: userRolesData, }; }) ); return usersWithRoles; } export async function getUserPermissionDetails(userId: number) { // 역할 기반 권한 const rolePermissionsData = await db .select({ id: permissions.id, permissionKey: permissions.permissionKey, name: permissions.name, description: permissions.description, permissionType: permissions.permissionType, resource: permissions.resource, action: permissions.action, scope: permissions.scope, menuPath: permissions.menuPath, roleName: roles.name, }) .from(userRoles) .innerJoin(roles, eq(roles.id, userRoles.roleId)) .innerJoin(rolePermissions, eq(rolePermissions.roleId, roles.id)) .innerJoin(permissions, eq(permissions.id, rolePermissions.permissionId)) .where(eq(userRoles.userId, userId)); // 직접 부여된 권한 const directPermissions = await db .select({ id: permissions.id, permissionKey: permissions.permissionKey, name: permissions.name, description: permissions.description, permissionType: permissions.permissionType, resource: permissions.resource, action: permissions.action, scope: permissions.scope, menuPath: permissions.menuPath, isGrant: userPermissions.isGrant, grantedBy: users.name, grantedAt: userPermissions.grantedAt, expiresAt: userPermissions.expiresAt, reason: userPermissions.reason, }) .from(userPermissions) .innerJoin(permissions, eq(permissions.id, userPermissions.permissionId)) .leftJoin(users, eq(users.id, userPermissions.grantedBy)) .where(eq(userPermissions.userId, userId)); // 모든 권한 목록 const allPermissions = await db.select().from(permissions); return { permissions: [ ...rolePermissionsData.map(p => ({ ...p, source: "role" as const })), ...directPermissions.map(p => ({ ...p, source: "direct" as const })), ], availablePermissions: allPermissions, }; } export async function grantPermissionToUser(params: { userId: number; permissionIds: number[]; isGrant: boolean; reason?: string; expiresAt?: Date; }) { const session = await getServerSession(authOptions) if (!session?.user?.id) { throw new Error("인증이 필요합니다.") } const currentUserId = Number(session.user.id) await db.transaction(async (tx) => { for (const permissionId of params.permissionIds) { await tx.insert(userPermissions).values({ userId: params.userId, permissionId, isGrant: params.isGrant, grantedBy: Number(session.user.id), reason: params.reason, expiresAt: params.expiresAt, }).onConflictDoUpdate({ target: [userPermissions.userId, userPermissions.permissionId], set: { isGrant: params.isGrant, grantedBy: Number(session.user.id), grantedAt: new Date(), reason: params.reason, expiresAt: params.expiresAt, isActive: true, } }); // 감사 로그 await tx.insert(permissionAuditLogs).values({ targetType: "user", targetId: params.userId, permissionId, action: params.isGrant ? "grant" : "restrict", performedBy: currentUserId, reason: params.reason, }); } }); } export async function revokePermissionFromUser(userId: number, permissionId: number) { const session = await getServerSession(authOptions) if (!session?.user?.id) { throw new Error("인증이 필요합니다.") } await db.transaction(async (tx) => { await tx.update(userPermissions) .set({ isActive: false }) .where( and( eq(userPermissions.userId, userId), eq(userPermissions.permissionId, permissionId) ) ); // 감사 로그 await tx.insert(permissionAuditLogs).values({ targetType: "user", targetId: userId, permissionId, action: "revoke", performedBy: Number(session.user.id), }); }); } export async function getMenuPermissions(domain: string = "all") { const menus = await db .select({ menuPath: menuAssignments.menuPath, menuTitle: menuAssignments.menuTitle, menuDescription: menuAssignments.menuDescription, sectionTitle: menuAssignments.sectionTitle, menuGroup: menuAssignments.menuGroup, domain: menuAssignments.domain, isActive: menuAssignments.isActive, manager1Id: menuAssignments.manager1Id, manager2Id: menuAssignments.manager2Id, }) .from(menuAssignments) .where(domain === "all" ? undefined : eq(menuAssignments.domain, domain)); // 각 메뉴의 권한과 담당자 정보 조회 const menusWithDetails = await Promise.all( menus.map(async (menu) => { // 필수 권한 조회 const requiredPerms = await db .select({ id: permissions.id, permissionKey: permissions.permissionKey, name: permissions.name, description: permissions.description, isRequired: menuRequiredPermissions.isRequired, }) .from(menuRequiredPermissions) .innerJoin(permissions, eq(permissions.id, menuRequiredPermissions.permissionId)) .where(eq(menuRequiredPermissions.menuPath, menu.menuPath)); // 담당자 정보 조회 const [manager1, manager2] = await Promise.all([ menu.manager1Id ? db.select({ id: users.id, name: users.name, email: users.email, imageUrl: users.imageUrl, }).from(users).where(eq(users.id, menu.manager1Id)).then(r => r[0]) : null, menu.manager2Id ? db.select({ id: users.id, name: users.name, email: users.email, imageUrl: users.imageUrl, }).from(users).where(eq(users.id, menu.manager2Id)).then(r => r[0]) : null, ]); return { ...menu, requiredPermissions: requiredPerms, manager1, manager2, }; }) ); // 사용 가능한 모든 권한 목록 const availablePermissions = await db.select().from(permissions); return { menus: menusWithDetails, availablePermissions, }; } export async function updateMenuPermissions( menuPath: string, permissions: Array<{ id: number; isRequired: boolean }> ) { await db.transaction(async (tx) => { // 기존 권한 삭제 await tx.delete(menuRequiredPermissions) .where(eq(menuRequiredPermissions.menuPath, menuPath)); // 새 권한 추가 if (permissions.length > 0) { await tx.insert(menuRequiredPermissions).values( permissions.map(p => ({ menuPath, permissionId: p.id, isRequired: p.isRequired, })) ); } }); } // 역할 목록 조회 export async function getRoles() { try { const rolesData = await db .select({ id: roles.id, name: roles.name, domain: roles.domain, description: roles.description, userCount: sql`count(distinct ${userRoles.userId})`.mapWith(Number), }) .from(roles) .leftJoin(userRoles, eq(userRoles.roleId, roles.id)) .groupBy(roles.id) .orderBy(roles.domain, roles.name); return rolesData; } catch (error) { console.log('Failed to get roles:', error); throw new Error('역할 목록 조회에 실패했습니다.'); } }