summaryrefslogtreecommitdiff
path: root/lib/permissions
diff options
context:
space:
mode:
Diffstat (limited to 'lib/permissions')
-rw-r--r--lib/permissions/permission-assignment-actions.ts83
-rw-r--r--lib/permissions/permission-group-actions.ts270
-rw-r--r--lib/permissions/permission-settings-actions.ts229
-rw-r--r--lib/permissions/service.ts434
4 files changed, 1016 insertions, 0 deletions
diff --git a/lib/permissions/permission-assignment-actions.ts b/lib/permissions/permission-assignment-actions.ts
new file mode 100644
index 00000000..75181c40
--- /dev/null
+++ b/lib/permissions/permission-assignment-actions.ts
@@ -0,0 +1,83 @@
+// app/actions/permission-assignment-actions.ts
+
+"use server";
+
+import db from "@/db/db";
+import { eq, and ,sql} from "drizzle-orm";
+import {
+ permissions,
+ roles,
+ rolePermissions,
+ users,
+ userPermissions,
+ userRoles
+} from "@/db/schema";
+
+// 권한별 할당 정보 조회
+export async function getPermissionAssignments(permissionId?: number) {
+ if (!permissionId) {
+ // 모든 권한 목록
+ const allPermissions = await db.select().from(permissions)
+ .where(eq(permissions.isActive, true))
+ .orderBy(permissions.resource, permissions.name);
+
+ return { permissions: allPermissions, roles: [], users: [] };
+ }
+
+ // 특정 권한의 할당 정보
+ const assignedRoles = await db
+ .select({
+ id: roles.id,
+ name: roles.name,
+ domain: roles.domain,
+ userCount: sql<number>`count(distinct ${userRoles.userId})`.mapWith(Number),
+ })
+ .from(rolePermissions)
+ .innerJoin(roles, eq(roles.id, rolePermissions.roleId))
+ .leftJoin(userRoles, eq(userRoles.roleId, roles.id))
+ .where(eq(rolePermissions.permissionId, permissionId))
+ .groupBy(roles.id);
+
+ const assignedUsers = await db
+ .select({
+ id: users.id,
+ name: users.name,
+ email: users.email,
+ imageUrl: users.imageUrl,
+ domain: users.domain,
+ isGrant: userPermissions.isGrant,
+ reason: userPermissions.reason,
+ })
+ .from(userPermissions)
+ .innerJoin(users, eq(users.id, userPermissions.userId))
+ .where(eq(userPermissions.permissionId, permissionId));
+
+ return {
+ permissions: [],
+ roles: assignedRoles,
+ users: assignedUsers,
+ };
+}
+
+// 역할에서 권한 제거
+export async function removePermissionFromRole(permissionId: number, roleId: number) {
+ await db.delete(rolePermissions)
+ .where(
+ and(
+ eq(rolePermissions.permissionId, permissionId),
+ eq(rolePermissions.roleId, roleId)
+ )
+ );
+}
+
+// 사용자에서 권한 제거
+export async function removePermissionFromUser(permissionId: number, userId: number) {
+ await db.update(userPermissions)
+ .set({ isActive: false })
+ .where(
+ and(
+ eq(userPermissions.permissionId, permissionId),
+ eq(userPermissions.userId, userId)
+ )
+ );
+} \ No newline at end of file
diff --git a/lib/permissions/permission-group-actions.ts b/lib/permissions/permission-group-actions.ts
new file mode 100644
index 00000000..51e3c2c0
--- /dev/null
+++ b/lib/permissions/permission-group-actions.ts
@@ -0,0 +1,270 @@
+// app/actions/permission-group-actions.ts
+
+"use server";
+
+import db from "@/db/db";
+import { eq, and, inArray, sql } from "drizzle-orm";
+import {
+ permissionGroups,
+ permissionGroupMembers,
+ permissions,
+ rolePermissions,
+ userPermissions,
+ roles,
+ users
+} from "@/db/schema";
+import { checkUserPermission } from "./service";
+import { getServerSession } from "next-auth/next"
+import { authOptions } from "@/app/api/auth/[...nextauth]/route"
+
+// 권한 그룹 목록 조회
+export async function getPermissionGroups() {
+ const groups = await db
+ .select({
+ id: permissionGroups.id,
+ groupKey: permissionGroups.groupKey,
+ name: permissionGroups.name,
+ description: permissionGroups.description,
+ domain: permissionGroups.domain,
+ isActive: permissionGroups.isActive,
+ createdAt: permissionGroups.createdAt,
+ updatedAt: permissionGroups.updatedAt,
+ permissionCount: sql<number>`count(distinct ${permissionGroupMembers.permissionId})`.mapWith(Number),
+ })
+ .from(permissionGroups)
+ .leftJoin(permissionGroupMembers, eq(permissionGroupMembers.groupId, permissionGroups.id))
+ .groupBy(permissionGroups.id)
+ .orderBy(permissionGroups.name);
+
+ // 각 그룹의 역할 및 사용자 수 계산
+ const groupsWithCounts = await Promise.all(
+ groups.map(async (group) => {
+ const roleCount = await db
+ .selectDistinct({ roleId: rolePermissions.roleId })
+ .from(rolePermissions)
+ .where(eq(rolePermissions.permissionGroupId, group.id));
+
+ const userCount = await db
+ .selectDistinct({ userId: userPermissions.userId })
+ .from(userPermissions)
+ .where(eq(userPermissions.permissionGroupId, group.id));
+
+ return {
+ ...group,
+ roleCount: roleCount.length,
+ userCount: userCount.length,
+ };
+ })
+ );
+
+ return groupsWithCounts;
+}
+
+// 권한 그룹 생성
+export async function createPermissionGroup(data: {
+ groupKey: string;
+ name: string;
+ description?: string;
+ domain?: 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(permissionGroups)
+ .where(eq(permissionGroups.groupKey, data.groupKey))
+ .limit(1);
+
+ if (existing.length > 0) {
+ throw new Error("이미 존재하는 그룹 키입니다.");
+ }
+
+ const [created] = await db.insert(permissionGroups).values(data).returning();
+ return created;
+}
+
+// 권한 그룹 수정
+export async function updatePermissionGroup(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(permissionGroups)
+ .set({
+ ...data,
+ updatedAt: new Date(),
+ })
+ .where(eq(permissionGroups.id, id))
+ .returning();
+
+ return updated;
+}
+
+// 권한 그룹 삭제
+export async function deletePermissionGroup(id: number) {
+ const currentUser = await getCurrentUser();
+ if (!currentUser) throw new Error("Unauthorized");
+
+ if (!await checkUserPermission(currentUser.id, "admin.permissions.manage")) {
+ throw new Error("권한 관리 권한이 없습니다.");
+ }
+
+ await db.transaction(async (tx) => {
+ // 그룹 멤버 삭제
+ await tx.delete(permissionGroupMembers)
+ .where(eq(permissionGroupMembers.groupId, id));
+
+ // 그룹 삭제
+ await tx.delete(permissionGroups)
+ .where(eq(permissionGroups.id, id));
+ });
+}
+
+// 그룹의 권한 조회
+export async function getGroupPermissions(groupId: number) {
+ const groupPermissions = 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,
+ })
+ .from(permissionGroupMembers)
+ .innerJoin(permissions, eq(permissions.id, permissionGroupMembers.permissionId))
+ .where(eq(permissionGroupMembers.groupId, groupId));
+
+ const allPermissions = await db.select().from(permissions)
+ .where(eq(permissions.isActive, true));
+
+ return {
+ permissions: groupPermissions,
+ availablePermissions: allPermissions,
+ };
+}
+
+// 그룹 권한 업데이트
+export async function updateGroupPermissions(groupId: 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(permissionGroupMembers)
+ .where(eq(permissionGroupMembers.groupId, groupId));
+
+ // 새 권한 추가
+ if (permissionIds.length > 0) {
+ await tx.insert(permissionGroupMembers).values(
+ permissionIds.map(permissionId => ({
+ groupId,
+ permissionId,
+ }))
+ );
+ }
+ });
+}
+
+// 권한 그룹 복제
+export async function clonePermissionGroup(groupId: 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("권한 관리 권한이 없습니다.");
+ }
+
+ // 원본 그룹 조회
+ const [originalGroup] = await db.select()
+ .from(permissionGroups)
+ .where(eq(permissionGroups.id, groupId));
+
+ if (!originalGroup) {
+ throw new Error("그룹을 찾을 수 없습니다.");
+ }
+
+ // 원본 그룹의 권한 조회
+ const originalPermissions = await db.select()
+ .from(permissionGroupMembers)
+ .where(eq(permissionGroupMembers.groupId, groupId));
+
+ // 새 그룹 생성
+ const timestamp = Date.now();
+ const [newGroup] = await db.insert(permissionGroups).values({
+ groupKey: `${originalGroup.groupKey}_copy_${timestamp}`,
+ name: `${originalGroup.name} (복사본)`,
+ description: originalGroup.description,
+ domain: originalGroup.domain,
+ isActive: originalGroup.isActive,
+ }).returning();
+
+ // 권한 복사
+ if (originalPermissions.length > 0) {
+ await db.insert(permissionGroupMembers).values(
+ originalPermissions.map(p => ({
+ groupId: newGroup.id,
+ permissionId: p.permissionId,
+ }))
+ );
+ }
+
+ return newGroup;
+}
+
+// 그룹 할당 정보 조회
+export async function getGroupAssignments(groupId: number) {
+ const assignedRoles = await db
+ .select({
+ id: roles.id,
+ name: roles.name,
+ domain: roles.domain,
+ })
+ .from(rolePermissions)
+ .innerJoin(roles, eq(roles.id, rolePermissions.roleId))
+ .where(eq(rolePermissions.permissionGroupId, groupId));
+
+ const assignedUsers = await db
+ .select({
+ id: users.id,
+ name: users.name,
+ email: users.email,
+ imageUrl: users.imageUrl,
+ domain: users.domain,
+ })
+ .from(userPermissions)
+ .innerJoin(users, eq(users.id, userPermissions.userId))
+ .where(eq(userPermissions.permissionGroupId, groupId));
+
+ return {
+ roles: assignedRoles,
+ users: assignedUsers,
+ };
+} \ No newline at end of file
diff --git a/lib/permissions/permission-settings-actions.ts b/lib/permissions/permission-settings-actions.ts
new file mode 100644
index 00000000..5d04a1d3
--- /dev/null
+++ b/lib/permissions/permission-settings-actions.ts
@@ -0,0 +1,229 @@
+// 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";
+
+// 모든 권한 조회
+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<number>`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 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));
+
+ // 제안할 권한 생성
+ const suggestedPermissions = [];
+ const resourceName = menu.menuPath.split('/').pop() || 'unknown';
+
+ // 기본 메뉴 접근 권한
+ suggestedPermissions.push({
+ permissionKey: `${resourceName}.menu_access`,
+ name: `${menu.menuTitle} 접근`,
+ 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: `${menu.menuTitle} ${name}`,
+ permissionType: type,
+ action,
+ scope: "assigned",
+ });
+ });
+
+ return {
+ menuPath: menu.menuPath,
+ menuTitle: menu.menuTitle,
+ 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 };
+} \ No newline at end of file
diff --git a/lib/permissions/service.ts b/lib/permissions/service.ts
new file mode 100644
index 00000000..3ef1ff04
--- /dev/null
+++ b/lib/permissions/service.ts
@@ -0,0 +1,434 @@
+// lib/permission/servicee.ts
+
+"use server";
+
+import db from "@/db/db";
+import { eq, and, inArray, or, ilike } 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) {
+ const allPermissions = await db.select().from(permissions)
+ .where(eq(permissions.isActive, true));
+
+ const rolePerms = await db.select({
+ permissionId: rolePermissions.permissionId,
+ })
+ .from(rolePermissions)
+ .where(eq(rolePermissions.roleId, roleId));
+
+ return {
+ permissions: allPermissions,
+ assignedPermissionIds: rolePerms.map(rp => rp.permissionId),
+ };
+}
+
+// 권한 체크 함수
+export async function checkUserPermission(
+ userId: number,
+ permissionKey: string
+): Promise<boolean> {
+ // 역할 기반 권한
+ 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<boolean> {
+ // 메뉴 담당자인 경우 자동 허용
+ 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,
+ }))
+ );
+ }
+ });
+} \ No newline at end of file