// app/actions/permission-settings-actions.ts "use server"; import db from "@/db/db"; import { eq, and, inArray, sql } from "drizzle-orm"; import { permissions, menuAssignments, menuRequiredPermissions } from "@/db/schema"; import { getServerSession } from "next-auth/next" import { authOptions } from "@/app/api/auth/[...nextauth]/route" import { checkUserPermission } from "./service"; import fs from 'fs/promises'; import path from 'path'; // i18n 번역 파일을 읽어오는 헬퍼 함수 async function getTranslations(locale: string = 'ko') { try { const filePath = path.join(process.cwd(), 'i18n', 'locales', locale, 'menu.json'); const fileContent = await fs.readFile(filePath, 'utf-8'); return JSON.parse(fileContent); } catch (error) { console.error(`Failed to load translations for ${locale}:`, error); return {}; } } // 중첩된 객체에서 키로 값을 가져오는 헬퍼 함수 function getNestedValue(obj: any, key: string): string { const keys = key.split('.'); let value = obj; for (const k of keys) { if (value && typeof value === 'object' && k in value) { value = value[k]; } else { return key; // 키를 찾지 못하면 원본 키를 반환 } } return typeof value === 'string' ? value : key; } // 모든 권한 조회 export async function getAllPermissions() { return await db.select().from(permissions).orderBy(permissions.resource, permissions.action); } // 권한 카테고리 조회 export async function getPermissionCategories() { const result = await db .select({ resource: permissions.resource, count: sql`count(*)`.mapWith(Number), }) .from(permissions) .groupBy(permissions.resource) .orderBy(permissions.resource); return result; } // 권한 생성 export async function createPermission(data: { permissionKey: string; name: string; description?: string; permissionType: string; resource: string; action: string; scope: string; menuPath?: string; uiElement?: string; isActive: boolean; }) { 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("권한 관리 권한이 없습니다."); } // 중복 체크 const existing = await db.select() .from(permissions) .where(eq(permissions.permissionKey, data.permissionKey)) .limit(1); if (existing.length > 0) { throw new Error("이미 존재하는 권한 키입니다."); } const [created] = await db.insert(permissions).values({ ...data, isSystem: false, }).returning(); return created; } // 권한 수정 export async function updatePermission(id: number, data: any) { 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("권한 관리 권한이 없습니다."); } const [updated] = await db.update(permissions) .set({ ...data, updatedAt: new Date(), }) .where(eq(permissions.id, id)) .returning(); return updated; } // 권한 삭제 export async function deletePermission(id: 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.delete(permissions).where(eq(permissions.id, id)); } // 메뉴 권한 분석 export async function analyzeMenuPermissions() { // 한국어 번역 파일 로드 const translations = await getTranslations('ko'); const menus = await db.select().from(menuAssignments); const analysis = await Promise.all( menus.map(async (menu) => { // 기존 권한 조회 const existing = await db .select({ id: permissions.id, permissionKey: permissions.permissionKey, name: permissions.name, }) .from(menuRequiredPermissions) .innerJoin(permissions, eq(permissions.id, menuRequiredPermissions.permissionId)) .where(eq(menuRequiredPermissions.menuPath, menu.menuPath)); // i18n 키를 실제 텍스트로 변환 const menuTitleTranslated = getNestedValue(translations, menu.menuTitle); const menuDescriptionTranslated = menu.menuDescription ? getNestedValue(translations, menu.menuDescription) : ''; // 제안할 권한 생성 const suggestedPermissions = []; const resourceName = menu.menuPath.split('/').pop() || 'unknown'; // 기본 메뉴 접근 권한 (번역된 제목 사용) suggestedPermissions.push({ permissionKey: `${resourceName}.menu_access`, name: `${menuTitleTranslated} 접근`, permissionType: "menu_access", action: "access", scope: "assigned", }); // CRUD 권한 제안 (번역된 제목 사용) const actions = [ { action: "view", name: "조회", type: "data_read" }, { action: "create", name: "생성", type: "data_write" }, { action: "update", name: "수정", type: "data_write" }, { action: "delete", name: "삭제", type: "data_delete" }, ]; actions.forEach(({ action, name, type }) => { suggestedPermissions.push({ permissionKey: `${resourceName}.${action}`, name: `${menuTitleTranslated} ${name}`, permissionType: type, action, scope: "assigned", }); }); return { menuPath: menu.menuPath, menuTitle: menuTitleTranslated, // 번역된 제목 menuTitleKey: menu.menuTitle, // 원본 i18n 키 (필요한 경우) menuDescription: menuDescriptionTranslated, // 번역된 설명 domain: menu.domain, existingPermissions: existing, suggestedPermissions: suggestedPermissions.filter( sp => !existing.some(ep => ep.permissionKey === sp.permissionKey) ), }; }) ); return analysis; } // 메뉴 기반 권한 생성 export async function generateMenuPermissions( permissionsToCreate: Array<{ permissionKey: string; name: string; permissionType: string; action: string; scope: string; menuPath: string; }> ) { 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("권한 관리 권한이 없습니다."); } let created = 0; let skipped = 0; await db.transaction(async (tx) => { for (const perm of permissionsToCreate) { // 중복 체크 const existing = await tx.select() .from(permissions) .where(eq(permissions.permissionKey, perm.permissionKey)) .limit(1); if (existing.length === 0) { const resource = perm.menuPath.split('/').pop() || 'unknown'; await tx.insert(permissions).values({ permissionKey: perm.permissionKey, name: perm.name, permissionType: perm.permissionType, resource, action: perm.action, scope: perm.scope, menuPath: perm.menuPath, isSystem: false, isActive: true, }); created++; } else { skipped++; } } }); return { created, skipped }; }