summaryrefslogtreecommitdiff
path: root/lib/permissions
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-09-29 13:31:40 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-09-29 13:31:40 +0000
commit4614210aa9878922cfa1e424ce677ef893a1b6b2 (patch)
tree5e7edcce05fbee207230af0a43ed08cd351d7c4f /lib/permissions
parente41e3af4e72870d44a94b03e0f3246d6ccaaca48 (diff)
(대표님) 구매 권한설정, data room 등
Diffstat (limited to 'lib/permissions')
-rw-r--r--lib/permissions/permission-group-actions.ts9
-rw-r--r--lib/permissions/permission-group-assignment-actions.ts496
-rw-r--r--lib/permissions/permission-settings-actions.ts54
-rw-r--r--lib/permissions/service.ts88
4 files changed, 625 insertions, 22 deletions
diff --git a/lib/permissions/permission-group-actions.ts b/lib/permissions/permission-group-actions.ts
index 51e3c2c0..474dc21b 100644
--- a/lib/permissions/permission-group-actions.ts
+++ b/lib/permissions/permission-group-actions.ts
@@ -117,10 +117,13 @@ export async function updatePermissionGroup(id: number, data: any) {
// 권한 그룹 삭제
export async function deletePermissionGroup(id: number) {
- const currentUser = await getCurrentUser();
- if (!currentUser) throw new Error("Unauthorized");
+ const session = await getServerSession(authOptions)
+ if (!session?.user?.id) {
+ throw new Error("인증이 필요합니다.")
+ }
+ const currentUserId = Number(session.user.id)
- if (!await checkUserPermission(currentUser.id, "admin.permissions.manage")) {
+ if (!await checkUserPermission(currentUserId, "admin.permissions.manage")) {
throw new Error("권한 관리 권한이 없습니다.");
}
diff --git a/lib/permissions/permission-group-assignment-actions.ts b/lib/permissions/permission-group-assignment-actions.ts
new file mode 100644
index 00000000..d1311559
--- /dev/null
+++ b/lib/permissions/permission-group-assignment-actions.ts
@@ -0,0 +1,496 @@
+// app/actions/permission-group-assignment-actions.ts
+
+"use server";
+
+import db from "@/db/db";
+import { eq, and, inArray, sql, ne, notInArray, or } from "drizzle-orm";
+import {
+ permissionGroups,
+ permissionGroupMembers,
+ permissions,
+ rolePermissions,
+ userPermissions,
+ roles,
+ users,
+ userRoles,
+ permissionAuditLogs
+} from "@/db/schema";
+import { checkUserPermission } from "./service";
+import { getServerSession } from "next-auth/next";
+import { authOptions } from "@/app/api/auth/[...nextauth]/route";
+
+// 권한 그룹 할당 정보 조회
+export async function getPermissionGroupAssignments(groupId?: number) {
+ try {
+ if (!groupId) {
+ // 모든 그룹 목록 반환
+ const groups = await db
+ .select({
+ id: permissionGroups.id,
+ groupKey: permissionGroups.groupKey,
+ name: permissionGroups.name,
+ description: permissionGroups.description,
+ domain: permissionGroups.domain,
+ isActive: permissionGroups.isActive,
+ permissionCount: sql<number>`count(distinct ${permissionGroupMembers.permissionId})`.mapWith(Number),
+ })
+ .from(permissionGroups)
+ .leftJoin(permissionGroupMembers, eq(permissionGroupMembers.groupId, permissionGroups.id))
+ .where(eq(permissionGroups.isActive, true))
+ .groupBy(permissionGroups.id)
+ .orderBy(permissionGroups.name);
+
+ return { groups };
+ }
+
+ // 특정 그룹의 할당 정보 조회
+ // 그룹에 속한 모든 권한 ID 조회
+ const groupPermissionIds = await db
+ .select({ permissionId: permissionGroupMembers.permissionId })
+ .from(permissionGroupMembers)
+ .where(eq(permissionGroupMembers.groupId, groupId));
+
+ const permissionIds = groupPermissionIds.map(p => p.permissionId);
+
+ if (permissionIds.length === 0) {
+ return { roles: [], users: [] };
+ }
+
+ // 해당 그룹의 권한들이 할당된 역할 조회
+ const assignedRoles = await db
+ .selectDistinct({
+ id: roles.id,
+ name: roles.name,
+ domain: roles.domain,
+ grantedAt: rolePermissions.grantedAt,
+ grantedBy: rolePermissions.grantedBy,
+ })
+ .from(rolePermissions)
+ .innerJoin(roles, eq(roles.id, rolePermissions.roleId))
+ .where(
+ and(
+ eq(rolePermissions.permissionGroupId, groupId),
+ eq(rolePermissions.isActive, true)
+ )
+ );
+
+ // 역할별 사용자 수 조회
+ const rolesWithUserCount = await Promise.all(
+ assignedRoles.map(async (role) => {
+ const userCount = await db
+ .select({ count: sql<number>`count(*)`.mapWith(Number) })
+ .from(userRoles)
+ .where(eq(userRoles.roleId, role.id));
+
+ // grantedBy가 userId인 경우 사용자 정보 조회
+ let assignedBy = 'system';
+ if (role.grantedBy) {
+ const [user] = await db
+ .select({ name: users.name })
+ .from(users)
+ .where(eq(users.id, role.grantedBy));
+ if (user) assignedBy = user.name;
+ }
+
+ return {
+ ...role,
+ userCount: userCount[0]?.count || 0,
+ assignedAt: role.grantedAt,
+ assignedBy
+ };
+ })
+ );
+
+ // 해당 그룹의 권한들이 직접 할당된 사용자 조회
+ const assignedUsers = await db
+ .selectDistinct({
+ id: users.id,
+ name: users.name,
+ email: users.email,
+ imageUrl: users.imageUrl,
+ domain: users.domain,
+ grantedAt: userPermissions.grantedAt,
+ grantedBy: userPermissions.grantedBy,
+ })
+ .from(userPermissions)
+ .innerJoin(users, eq(users.id, userPermissions.userId))
+ .where(
+ and(
+ eq(userPermissions.permissionGroupId, groupId),
+ eq(userPermissions.isActive, true),
+ eq(userPermissions.isGrant, true)
+ )
+ );
+
+ // 사용자별 회사 정보 및 할당자 정보 추가
+ const usersWithDetails = await Promise.all(
+ assignedUsers.map(async (user) => {
+ // 회사 정보는 companyId로 조회 (vendors 테이블 필요)
+ let assignedBy = 'system';
+ if (user.grantedBy) {
+ const [grantUser] = await db
+ .select({ name: users.name })
+ .from(users)
+ .where(eq(users.id, user.grantedBy));
+ if (grantUser) assignedBy = grantUser.name;
+ }
+
+ return {
+ ...user,
+ companyName: null, // 필요시 vendors 테이블 조인
+ assignedAt: user.grantedAt,
+ assignedBy
+ };
+ })
+ );
+
+ return {
+ roles: rolesWithUserCount,
+ users: usersWithDetails
+ };
+ } catch (error) {
+ console.error('Failed to get permission group assignments:', error);
+ throw new Error('권한 그룹 할당 정보 조회에 실패했습니다.');
+ }
+}
+
+// 역할 검색 (그룹에 아직 할당되지 않은)
+export async function searchRoles(groupId: number) {
+ try {
+ // 이미 해당 그룹이 할당된 역할 ID 조회
+ const assignedRoleIds = await db
+ .selectDistinct({ roleId: rolePermissions.roleId })
+ .from(rolePermissions)
+ .where(
+ and(
+ eq(rolePermissions.permissionGroupId, groupId),
+ eq(rolePermissions.isActive, true)
+ )
+ );
+
+ const assignedIds = assignedRoleIds.map(r => r.roleId);
+
+ // 할당되지 않은 역할 조회
+ const availableRoles = await db
+ .select({
+ id: roles.id,
+ name: roles.name,
+ domain: roles.domain,
+ })
+ .from(roles)
+ .where(
+ assignedIds.length > 0
+ ? notInArray(roles.id, assignedIds)
+ : undefined
+ );
+
+ // 역할별 사용자 수 추가
+ const rolesWithUserCount = await Promise.all(
+ availableRoles.map(async (role) => {
+ const userCount = await db
+ .select({ count: sql<number>`count(*)`.mapWith(Number) })
+ .from(userRoles)
+ .where(eq(userRoles.roleId, role.id));
+
+ return {
+ ...role,
+ userCount: userCount[0]?.count || 0
+ };
+ })
+ );
+
+ return rolesWithUserCount;
+ } catch (error) {
+ console.error('Failed to search roles:', error);
+ throw new Error('역할 검색에 실패했습니다.');
+ }
+}
+
+// 사용자 검색 (그룹에 아직 할당되지 않은)
+export async function searchUsers(query: string, groupId: number) {
+ try {
+ // 이미 해당 그룹이 할당된 사용자 ID 조회
+ const assignedUserIds = await db
+ .selectDistinct({ userId: userPermissions.userId })
+ .from(userPermissions)
+ .where(
+ and(
+ eq(userPermissions.permissionGroupId, groupId),
+ eq(userPermissions.isActive, true),
+ eq(userPermissions.isGrant, true)
+ )
+ );
+
+ const assignedIds = assignedUserIds.map(u => u.userId);
+
+ // 할당되지 않은 사용자 검색
+ const availableUsers = await db
+ .select({
+ id: users.id,
+ name: users.name,
+ email: users.email,
+ imageUrl: users.imageUrl,
+ domain: users.domain,
+ })
+ .from(users)
+ .where(
+ and(
+ or(
+ sql`${users.name} ILIKE ${`%${query}%`}`,
+ sql`${users.email} ILIKE ${`%${query}%`}`
+ ),
+ eq(users.isActive, true),
+ assignedIds.length > 0
+ ? notInArray(users.id, assignedIds)
+ : undefined
+ )
+ )
+ .limit(20);
+
+ return availableUsers.map(user => ({
+ ...user,
+ companyName: null // 필요시 vendors 테이블 조인
+ }));
+ } catch (error) {
+ console.error('Failed to search users:', error);
+ throw new Error('사용자 검색에 실패했습니다.');
+ }
+}
+
+// 그룹을 역할에 할당
+export async function assignGroupToRoles(groupId: number, roleIds: 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("권한 관리 권한이 없습니다.");
+ }
+
+ try {
+ // 그룹에 속한 모든 권한 ID 조회
+ const groupPermissions = await db
+ .select({ permissionId: permissionGroupMembers.permissionId })
+ .from(permissionGroupMembers)
+ .where(eq(permissionGroupMembers.groupId, groupId));
+
+ if (groupPermissions.length === 0) {
+ throw new Error("그룹에 권한이 없습니다.");
+ }
+
+ await db.transaction(async (tx) => {
+ for (const roleId of roleIds) {
+ // 각 역할에 대해 그룹의 모든 권한 할당
+ for (const { permissionId } of groupPermissions) {
+ // 기존 할당 확인 (중복 방지)
+ const existing = await tx
+ .select()
+ .from(rolePermissions)
+ .where(
+ and(
+ eq(rolePermissions.roleId, roleId),
+ eq(rolePermissions.permissionId, permissionId),
+ eq(rolePermissions.permissionGroupId, groupId)
+ )
+ )
+ .limit(1);
+
+ if (existing.length === 0) {
+ await tx.insert(rolePermissions).values({
+ roleId,
+ permissionId,
+ permissionGroupId: groupId,
+ grantedBy: currentUserId,
+ grantedAt: new Date(),
+ isActive: true
+ });
+ }
+ }
+
+ // 감사 로그 추가
+ await tx.insert(permissionAuditLogs).values({
+ targetType: 'role',
+ targetId: roleId,
+ permissionGroupId: groupId,
+ action: 'grant',
+ performedBy: currentUserId,
+ reason: `권한 그룹 ${groupId} 할당`
+ });
+ }
+ });
+
+ return { success: true, count: roleIds.length };
+ } catch (error) {
+ console.error('Failed to assign group to roles:', error);
+ throw new Error('역할에 권한 그룹 할당에 실패했습니다.');
+ }
+}
+
+// 그룹을 사용자에 할당
+export async function assignGroupToUsers(groupId: number, userIds: 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("권한 관리 권한이 없습니다.");
+ }
+
+ try {
+ // 그룹에 속한 모든 권한 ID 조회
+ const groupPermissions = await db
+ .select({ permissionId: permissionGroupMembers.permissionId })
+ .from(permissionGroupMembers)
+ .where(eq(permissionGroupMembers.groupId, groupId));
+
+ if (groupPermissions.length === 0) {
+ throw new Error("그룹에 권한이 없습니다.");
+ }
+
+ await db.transaction(async (tx) => {
+ for (const userId of userIds) {
+ // 각 사용자에 대해 그룹의 모든 권한 할당
+ for (const { permissionId } of groupPermissions) {
+ // 기존 할당 확인 (중복 방지)
+ const existing = await tx
+ .select()
+ .from(userPermissions)
+ .where(
+ and(
+ eq(userPermissions.userId, userId),
+ eq(userPermissions.permissionId, permissionId)
+ )
+ )
+ .limit(1);
+
+ if (existing.length === 0) {
+ await tx.insert(userPermissions).values({
+ userId,
+ permissionId,
+ permissionGroupId: groupId,
+ isGrant: true,
+ grantedBy: currentUserId,
+ grantedAt: new Date(),
+ isActive: true
+ });
+ } else if (existing[0].permissionGroupId !== groupId) {
+ // 다른 그룹으로 할당되어 있다면 업데이트
+ await tx.update(userPermissions)
+ .set({
+ permissionGroupId: groupId,
+ grantedBy: currentUserId,
+ grantedAt: new Date()
+ })
+ .where(
+ and(
+ eq(userPermissions.userId, userId),
+ eq(userPermissions.permissionId, permissionId)
+ )
+ );
+ }
+ }
+
+ // 감사 로그 추가
+ await tx.insert(permissionAuditLogs).values({
+ targetType: 'user',
+ targetId: userId,
+ permissionGroupId: groupId,
+ action: 'grant',
+ performedBy: currentUserId,
+ reason: `권한 그룹 ${groupId} 할당`
+ });
+ }
+ });
+
+ return { success: true, count: userIds.length };
+ } catch (error) {
+ console.error('Failed to assign group to users:', error);
+ throw new Error('사용자에게 권한 그룹 할당에 실패했습니다.');
+ }
+}
+
+// 역할에서 그룹 제거
+export async function removeGroupFromRole(groupId: number, roleId: 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("권한 관리 권한이 없습니다.");
+ }
+
+ try {
+ await db.transaction(async (tx) => {
+ // 해당 그룹으로 할당된 모든 권한 제거
+ await tx.delete(rolePermissions)
+ .where(
+ and(
+ eq(rolePermissions.roleId, roleId),
+ eq(rolePermissions.permissionGroupId, groupId)
+ )
+ );
+
+ // 감사 로그 추가
+ await tx.insert(permissionAuditLogs).values({
+ targetType: 'role',
+ targetId: roleId,
+ permissionGroupId: groupId,
+ action: 'revoke',
+ performedBy: currentUserId,
+ reason: `권한 그룹 ${groupId} 제거`
+ });
+ });
+
+ return { success: true };
+ } catch (error) {
+ console.error('Failed to remove group from role:', error);
+ throw new Error('역할에서 권한 그룹 제거에 실패했습니다.');
+ }
+}
+
+// 사용자에서 그룹 제거
+export async function removeGroupFromUser(groupId: number, userId: 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("권한 관리 권한이 없습니다.");
+ }
+
+ try {
+ await db.transaction(async (tx) => {
+ // 해당 그룹으로 할당된 모든 권한 제거
+ await tx.delete(userPermissions)
+ .where(
+ and(
+ eq(userPermissions.userId, userId),
+ eq(userPermissions.permissionGroupId, groupId)
+ )
+ );
+
+ // 감사 로그 추가
+ await tx.insert(permissionAuditLogs).values({
+ targetType: 'user',
+ targetId: userId,
+ permissionGroupId: groupId,
+ action: 'revoke',
+ performedBy: currentUserId,
+ reason: `권한 그룹 ${groupId} 제거`
+ });
+ });
+
+ return { success: true };
+ } catch (error) {
+ console.error('Failed to remove group from user:', error);
+ throw new Error('사용자에서 권한 그룹 제거에 실패했습니다.');
+ }
+} \ No newline at end of file
diff --git a/lib/permissions/permission-settings-actions.ts b/lib/permissions/permission-settings-actions.ts
index 5d04a1d3..bb82b456 100644
--- a/lib/permissions/permission-settings-actions.ts
+++ b/lib/permissions/permission-settings-actions.ts
@@ -12,6 +12,37 @@ import {
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() {
@@ -110,7 +141,11 @@ export async function deletePermission(id: number) {
}
// 메뉴 권한 분석
+
export async function analyzeMenuPermissions() {
+ // 한국어 번역 파일 로드
+ const translations = await getTranslations('ko');
+
const menus = await db.select().from(menuAssignments);
const analysis = await Promise.all(
@@ -126,20 +161,26 @@ export async function analyzeMenuPermissions() {
.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: `${menu.menuTitle} 접근`,
+ name: `${menuTitleTranslated} 접근`,
permissionType: "menu_access",
action: "access",
scope: "assigned",
});
- // CRUD 권한 제안
+ // CRUD 권한 제안 (번역된 제목 사용)
const actions = [
{ action: "view", name: "조회", type: "data_read" },
{ action: "create", name: "생성", type: "data_write" },
@@ -150,7 +191,7 @@ export async function analyzeMenuPermissions() {
actions.forEach(({ action, name, type }) => {
suggestedPermissions.push({
permissionKey: `${resourceName}.${action}`,
- name: `${menu.menuTitle} ${name}`,
+ name: `${menuTitleTranslated} ${name}`,
permissionType: type,
action,
scope: "assigned",
@@ -159,7 +200,9 @@ export async function analyzeMenuPermissions() {
return {
menuPath: menu.menuPath,
- menuTitle: menu.menuTitle,
+ menuTitle: menuTitleTranslated, // 번역된 제목
+ menuTitleKey: menu.menuTitle, // 원본 i18n 키 (필요한 경우)
+ menuDescription: menuDescriptionTranslated, // 번역된 설명
domain: menu.domain,
existingPermissions: existing,
suggestedPermissions: suggestedPermissions.filter(
@@ -172,6 +215,7 @@ export async function analyzeMenuPermissions() {
return analysis;
}
+
// 메뉴 기반 권한 생성
export async function generateMenuPermissions(
permissionsToCreate: Array<{
diff --git a/lib/permissions/service.ts b/lib/permissions/service.ts
index 3ef1ff04..b3e6b4bc 100644
--- a/lib/permissions/service.ts
+++ b/lib/permissions/service.ts
@@ -3,7 +3,7 @@
"use server";
import db from "@/db/db";
-import { eq, and, inArray, or, ilike } from "drizzle-orm";
+import { eq, and, inArray, or, ilike, sql } from "drizzle-orm";
import {
permissions,
rolePermissions,
@@ -70,21 +70,58 @@ export async function assignPermissionsToRole(
// 역할의 권한 목록 조회
+// 역할 권한 조회 (기존 함수)
export async function getRolePermissions(roleId: number) {
- const allPermissions = await db.select().from(permissions)
- .where(eq(permissions.isActive, true));
-
- const rolePerms = await db.select({
- permissionId: rolePermissions.permissionId,
- })
+ 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)
- .where(eq(rolePermissions.roleId, roleId));
-
- return {
+ .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: rolePerms.map(rp => rp.permissionId),
- };
-}
+ assignedPermissionIds: assignedPermissions.map(p => p.id)
+ };
+ } catch (error) {
+ console.error('Failed to get role permissions:', error);
+ throw new Error('역할 권한 조회에 실패했습니다.');
+ }
+ }
+
// 권한 체크 함수
export async function checkUserPermission(
@@ -431,4 +468,27 @@ export async function updateMenuPermissions(
);
}
});
-} \ No newline at end of file
+}
+
+// 역할 목록 조회
+export async function getRoles() {
+ try {
+ const rolesData = await db
+ .select({
+ id: roles.id,
+ name: roles.name,
+ domain: roles.domain,
+ description: roles.description,
+ userCount: sql<number>`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('역할 목록 조회에 실패했습니다.');
+ }
+ } \ No newline at end of file