- executeServerAction 공통 유틸 도입으로 actions.ts 대폭 간소화 (50+개 파일) - sanitize 유틸 추가 (XSS 방지) - middleware CSP 헤더 추가 및 Open Redirect 방지 - 프록시 라우트 로깅 개발환경 한정으로 변경 - 프로덕션 불필요 console.log 제거 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
139 lines
5.1 KiB
TypeScript
139 lines
5.1 KiB
TypeScript
'use server';
|
|
|
|
|
|
import { executeServerAction, type ActionResult } from '@/lib/api/execute-server-action';
|
|
import { revalidatePath } from 'next/cache';
|
|
import type { Role, RoleStats, PermissionMatrix, MenuTreeItem, PaginatedResponse } from './types';
|
|
|
|
const API_URL = process.env.NEXT_PUBLIC_API_URL;
|
|
|
|
// ========== Role CRUD ==========
|
|
|
|
export async function fetchRoles(params?: {
|
|
page?: number; size?: number; q?: string; is_hidden?: boolean;
|
|
}): Promise<ActionResult<PaginatedResponse<Role>>> {
|
|
const searchParams = new URLSearchParams();
|
|
if (params?.page) searchParams.set('page', params.page.toString());
|
|
if (params?.size) searchParams.set('per_page', params.size.toString());
|
|
if (params?.q) searchParams.set('q', params.q);
|
|
if (params?.is_hidden !== undefined) searchParams.set('is_hidden', params.is_hidden.toString());
|
|
const queryString = searchParams.toString();
|
|
|
|
return executeServerAction<PaginatedResponse<Role>>({
|
|
url: `${API_URL}/api/v1/roles${queryString ? `?${queryString}` : ''}`,
|
|
errorMessage: '역할 목록 조회에 실패했습니다.',
|
|
});
|
|
}
|
|
|
|
export async function fetchRole(id: number): Promise<ActionResult<Role>> {
|
|
return executeServerAction<Role>({
|
|
url: `${API_URL}/api/v1/roles/${id}`,
|
|
errorMessage: '역할 조회에 실패했습니다.',
|
|
});
|
|
}
|
|
|
|
export async function createRole(data: {
|
|
name: string; description?: string; is_hidden?: boolean;
|
|
}): Promise<ActionResult<Role>> {
|
|
const result = await executeServerAction<Role>({
|
|
url: `${API_URL}/api/v1/roles`,
|
|
method: 'POST',
|
|
body: data,
|
|
errorMessage: '역할 생성에 실패했습니다.',
|
|
});
|
|
if (result.success) revalidatePath('/settings/permissions');
|
|
return result;
|
|
}
|
|
|
|
export async function updateRole(id: number, data: {
|
|
name?: string; description?: string; is_hidden?: boolean;
|
|
}): Promise<ActionResult<Role>> {
|
|
const result = await executeServerAction<Role>({
|
|
url: `${API_URL}/api/v1/roles/${id}`,
|
|
method: 'PATCH',
|
|
body: data,
|
|
errorMessage: '역할 수정에 실패했습니다.',
|
|
});
|
|
if (result.success) {
|
|
revalidatePath('/settings/permissions');
|
|
revalidatePath(`/settings/permissions/${id}`);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
export async function deleteRole(id: number): Promise<ActionResult> {
|
|
const result = await executeServerAction({
|
|
url: `${API_URL}/api/v1/roles/${id}`,
|
|
method: 'DELETE',
|
|
errorMessage: '역할 삭제에 실패했습니다.',
|
|
});
|
|
if (result.success) revalidatePath('/settings/permissions');
|
|
return result;
|
|
}
|
|
|
|
export async function fetchRoleStats(): Promise<ActionResult<RoleStats>> {
|
|
return executeServerAction<RoleStats>({
|
|
url: `${API_URL}/api/v1/roles/stats`,
|
|
errorMessage: '역할 통계 조회에 실패했습니다.',
|
|
});
|
|
}
|
|
|
|
export async function fetchActiveRoles(): Promise<ActionResult<Role[]>> {
|
|
return executeServerAction<Role[]>({
|
|
url: `${API_URL}/api/v1/roles/active`,
|
|
errorMessage: '활성 역할 목록 조회에 실패했습니다.',
|
|
});
|
|
}
|
|
|
|
// ========== Permission Matrix ==========
|
|
|
|
export async function fetchPermissionMenus(): Promise<ActionResult<{
|
|
menus: MenuTreeItem[]; permission_types: string[];
|
|
}>> {
|
|
return executeServerAction({
|
|
url: `${API_URL}/api/v1/role-permissions/menus`,
|
|
errorMessage: '메뉴 트리 조회에 실패했습니다.',
|
|
});
|
|
}
|
|
|
|
export async function fetchPermissionMatrix(roleId: number): Promise<ActionResult<PermissionMatrix>> {
|
|
return executeServerAction<PermissionMatrix>({
|
|
url: `${API_URL}/api/v1/roles/${roleId}/permissions/matrix`,
|
|
errorMessage: '권한 매트릭스 조회에 실패했습니다.',
|
|
});
|
|
}
|
|
|
|
export async function togglePermission(roleId: number, menuId: number, permissionType: string): Promise<ActionResult<{
|
|
granted: boolean; propagated_to: number[];
|
|
}>> {
|
|
const result = await executeServerAction<{ granted: boolean; propagated_to: number[] }>({
|
|
url: `${API_URL}/api/v1/roles/${roleId}/permissions/toggle`,
|
|
method: 'POST',
|
|
body: { menu_id: menuId, permission_type: permissionType },
|
|
errorMessage: '권한 토글에 실패했습니다.',
|
|
});
|
|
if (result.success) revalidatePath(`/settings/permissions/${roleId}`);
|
|
return result;
|
|
}
|
|
|
|
async function rolePermissionAction(roleId: number, action: string, errorMessage: string): Promise<ActionResult<{ count: number }>> {
|
|
const result = await executeServerAction<{ count: number }>({
|
|
url: `${API_URL}/api/v1/roles/${roleId}/permissions/${action}`,
|
|
method: 'POST',
|
|
errorMessage,
|
|
});
|
|
if (result.success) revalidatePath(`/settings/permissions/${roleId}`);
|
|
return result;
|
|
}
|
|
|
|
export async function allowAllPermissions(roleId: number): Promise<ActionResult<{ count: number }>> {
|
|
return rolePermissionAction(roleId, 'allow-all', '전체 허용에 실패했습니다.');
|
|
}
|
|
|
|
export async function denyAllPermissions(roleId: number): Promise<ActionResult<{ count: number }>> {
|
|
return rolePermissionAction(roleId, 'deny-all', '전체 거부에 실패했습니다.');
|
|
}
|
|
|
|
export async function resetPermissions(roleId: number): Promise<ActionResult<{ count: number }>> {
|
|
return rolePermissionAction(roleId, 'reset', '권한 초기화에 실패했습니다.');
|
|
} |