폴더단위 권한 제어 기능 추가

This commit is contained in:
koj729
2026-06-15 13:51:06 +09:00
parent 4e33c9a02a
commit d13c414d7f
15 changed files with 1324 additions and 129 deletions

View File

@@ -1,11 +1,18 @@
/**
* [변경 이력 (Auto-Generated by AI)]
* - 수정일시: 2026-06-15 11:40:00
* - 수정원인: 폴더별 권한 관리 트리 노드의 data_permission 기준 정렬 요구사항 반영
* - 수정내용: getFolderPermissions API의 foldersQuery에 data_permission 컬럼을 조회하도록 SELECT절 추가
*/
const pool = require("../../db/pool.js");
const crypto = require("crypto");
const env = process.env.NODE_ENV;
const tbProject = env === 'production' ? 'tb_project' : '_test_tb_project';
const tbData = env === 'production' ? 'tb_data' : '_test_tb_data';
const tbLog = 'tb_log';
const tbLog = env === 'production' ? 'tb_log' : '_test_tb_log';
const tbPermission = env === 'production' ? 'tb_permission' : '_test_tb_permission';
const tbFolderPermission = env === 'production' ? 'tb_folder_permission' : '_test_tb_folder_permission';
// 감사 로그(Audit Log) 삽입 헬퍼 함수 (메인 트랜잭션에 영향을 주지 않기 위해 pool.query 사용)
@@ -578,34 +585,39 @@ exports.deleteUser = async (req, res) => {
}
};
// 5. 감사 로그 조회 (Audit Logs)
// 5. 활동 로그 조회 (Activity Logs)
exports.getAuditLogs = async (req, res) => {
const { user_id, activity } = req.query;
const { user_id, activity, project_nm } = req.query;
const client = await pool.connect();
try {
let query = `
SELECT log_id, log_date as clean_date, project_id, user_id, user_ip, activity as clean_path, path_arr as criteria_info
FROM ver4.${tbLog}
SELECT l.log_id, l.log_date as clean_date, l.project_id, p.project_nm, l.user_id, l.user_ip, l.activity as clean_path, l.path_arr as criteria_info
FROM ver4.${tbLog} l
LEFT JOIN ver4.${tbProject} p ON l.project_id = p.project_id
WHERE 1=1
`;
const params = [];
let paramIndex = 1;
if (user_id) {
query += ` AND user_id ILIKE $${paramIndex++}`;
query += ` AND l.user_id ILIKE $${paramIndex++}`;
params.push(`%${user_id}%`);
}
if (project_nm) {
query += ` AND (p.project_nm ILIKE $${paramIndex++} OR l.project_id ILIKE $${paramIndex - 1})`;
params.push(`%${project_nm}%`);
}
if (activity && activity !== 'all') {
query += ` AND activity = $${paramIndex++}`;
params.push(activity);
query += ` AND l.activity ILIKE $${paramIndex++}`;
params.push(`%${activity}%`);
}
query += ` ORDER BY log_id DESC LIMIT 100;`;
query += ` ORDER BY l.log_id DESC LIMIT 100;`;
const result = await client.query(query, params);
res.status(200).json(result.rows);
} catch (err) {
console.error("getAuditLogs Error:", err);
res.status(500).json({ error: "감사 로그 조회 실패" });
res.status(500).json({ error: "활동 로그 조회 실패" });
} finally {
client.release();
}
@@ -902,3 +914,118 @@ exports.deleteCodeDetail = async (req, res) => {
client.release();
}
};
// 2-1. Folder-Level Permissions
exports.getFolderPermissions = async (req, res) => {
const { projectId } = req.params;
const client = await pool.connect();
try {
// 1. Fetch folders (depth <= 3)
const foldersQuery = `
SELECT data_id, path1, path2, path3, data_depth, is_folder, data_permission
FROM ver4.${tbData}
WHERE project_id = $1 AND is_folder = true AND is_removed = false AND data_depth <= 3
ORDER BY path1, path2, path3;
`;
const foldersRes = await client.query(foldersQuery, [projectId]);
// 2. Fetch current folder permissions
const folderPermsQuery = `
SELECT fp.folder_permission_id, fp.folder_path_key, fp.user_id, fp.lev, u.user_nm
FROM ver4.${tbFolderPermission} fp
JOIN ver4.tb_user u ON fp.user_id = u.user_id
WHERE fp.project_id = $1;
`;
const folderPermsRes = await client.query(folderPermsQuery, [projectId]);
// 3. Fetch project users
const usersQuery = `
SELECT pm.user_id, u.user_nm, u.company, u.dept, u.position, pm.lev as project_lev
FROM ver4.${tbPermission} pm
JOIN ver4.tb_user u ON pm.user_id = u.user_id
WHERE pm.project_id = $1
ORDER BY u.user_nm ASC;
`;
const usersRes = await client.query(usersQuery, [projectId]);
res.status(200).json({
folders: foldersRes.rows,
folderPermissions: folderPermsRes.rows,
users: usersRes.rows
});
} catch (err) {
console.error("getFolderPermissions Error:", err);
res.status(500).json({ error: "폴더 권한 정보 조회 실패" });
} finally {
client.release();
}
};
exports.assignFolderPermissions = async (req, res) => {
const { project_id, folder_path_key, user_id, lev } = req.body;
if (!project_id || !folder_path_key || !user_id || lev === undefined) {
return res.status(400).json({ error: "필수 파라미터가 누락되었습니다." });
}
const client = await pool.connect();
try {
const query = `
INSERT INTO ver4.${tbFolderPermission} (project_id, folder_path_key, user_id, lev, mod_date)
VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP)
ON CONFLICT (project_id, folder_path_key, user_id)
DO UPDATE SET lev = EXCLUDED.lev, mod_date = CURRENT_TIMESTAMP
RETURNING *;
`;
const result = await client.query(query, [project_id, folder_path_key, user_id, lev]);
const userIp = req.headers['cf-connecting-ip'] || req.ip || req.headers['x-forwarded-for'] || req.connection?.remoteAddress;
await insertAuditLog(project_id, 'assignFolderPermission', req.user?.user_id, userIp, [
`Folder path: ${folder_path_key}`,
`Assigned user_id: ${user_id}`,
`Folder level assigned: ${lev}`
]);
res.status(200).json({
message: "폴더 권한이 성공적으로 부여되었습니다.",
data: result.rows[0]
});
} catch (err) {
console.error("assignFolderPermissions Error:", err);
res.status(500).json({ error: "폴더 권한 부여 실패" });
} finally {
client.release();
}
};
exports.removeFolderPermission = async (req, res) => {
const { project_id, folder_path_key, user_id } = req.body;
if (!project_id || !folder_path_key || !user_id) {
return res.status(400).json({ error: "필수 파라미터가 누락되었습니다." });
}
const client = await pool.connect();
try {
const query = `
DELETE FROM ver4.${tbFolderPermission}
WHERE project_id = $1 AND folder_path_key = $2 AND user_id = $3
RETURNING *;
`;
const result = await client.query(query, [project_id, folder_path_key, user_id]);
if (result.rows.length === 0) {
return res.status(404).json({ error: "대상을 찾을 수 없습니다." });
}
const userIp = req.headers['cf-connecting-ip'] || req.ip || req.headers['x-forwarded-for'] || req.connection?.remoteAddress;
await insertAuditLog(project_id, 'removeFolderPermission', req.user?.user_id, userIp, [
`Folder path: ${folder_path_key}`,
`Removed user_id: ${user_id}`
]);
res.status(200).json({ message: "폴더 권한이 제거되었으며 상속 상태로 초기화되었습니다." });
} catch (err) {
console.error("removeFolderPermission Error:", err);
res.status(500).json({ error: "폴더 권한 삭제 실패" });
} finally {
client.release();
}
};

View File

@@ -43,11 +43,12 @@ const cloudType = process.env.CLOUD_TYPE;
//// env의 NODE_ENV에 따라 DB 테이블 이름 설정
const env = process.env.NODE_ENV;
const tbLog = 'tb_log';
const tbLog = env === 'production' ? 'tb_log' : '_test_tb_log';
const tbData = env == 'production'? 'tb_data':'_test_tb_data';
const tbClickLog = env == 'production'? 'tb_click_log':'_test_tb_click_log';
const tbProject = env === 'production' ? 'tb_project' : '_test_tb_project';
const tbPermission = env === 'production' ? 'tb_permission' : '_test_tb_permission';
const tbFolderPermission = env === 'production' ? 'tb_folder_permission' : '_test_tb_folder_permission';
// 테스트
// const tbLog = env == 'production'? 'tb_log':'tb_log';
@@ -281,16 +282,23 @@ async function selectData(projectId, storageType, userInfo, resourcePath) {
}
queryString += `
AND ((d.data_permission+32)&`;
if (permission) {
queryString += `$${paramCounter++}`;
values.push(permission);
} else {
queryString += `(SELECT lev FROM ver4.${tbPermission} WHERE project_id = $${paramCounter++} AND user_id = $${paramCounter++})`;
values.push(projectId);
values.push(userId);
}
queryString += `) <> 0`;
AND ((d.data_permission+32) & COALESCE(
(SELECT lev FROM ver4.${tbFolderPermission}
WHERE project_id = d.project_id
AND user_id = $${paramCounter++}
AND folder_path_key = CASE
WHEN d.data_depth = 1 THEN d.path1
WHEN d.data_depth = 2 THEN CONCAT(d.path1, '/', d.path2)
ELSE CONCAT(d.path1, '/', d.path2, '/', d.path3)
END),
(SELECT lev FROM ver4.${tbPermission}
WHERE project_id = d.project_id
AND user_id = $${paramCounter++}),
$${paramCounter++}::integer
)) <> 0`;
values.push(userId);
values.push(userId);
values.push(parseInt(permission) || 0);
queryString += `
ORDER BY path1, path2, path3, path4, path5, path6, path7, path8;`;
@@ -425,16 +433,23 @@ async function selectRemovedData(projectId, storageType, userInfo) {
values.push(storageType);
queryString += `
AND ((d.data_permission+32)&`;
if (permission) {
queryString += `$${paramCounter++}`;
values.push(permission);
} else {
queryString += `(SELECT lev FROM ver4.${tbPermission} WHERE project_id = $${paramCounter++} AND user_id = $${paramCounter++})`;
values.push(projectId);
values.push(userId);
}
queryString += `) <> 0`;
AND ((d.data_permission+32) & COALESCE(
(SELECT lev FROM ver4.${tbFolderPermission}
WHERE project_id = d.project_id
AND user_id = $${paramCounter++}
AND folder_path_key = CASE
WHEN d.data_depth = 1 THEN d.path1
WHEN d.data_depth = 2 THEN CONCAT(d.path1, '/', d.path2)
ELSE CONCAT(d.path1, '/', d.path2, '/', d.path3)
END),
(SELECT lev FROM ver4.${tbPermission}
WHERE project_id = d.project_id
AND user_id = $${paramCounter++}),
$${paramCounter++}::integer
)) <> 0`;
values.push(userId);
values.push(userId);
values.push(parseInt(permission) || 0);
queryString += `
ORDER BY path1, path2, path3, path4, path5, path6, path7, path8;`;
@@ -773,6 +788,7 @@ async function insertData(params) {
return result;
} catch(error) {
console.error("insertData err:", error);
return { message: 'insertData_failed', error: error };
} finally {
client.release();
}
@@ -848,6 +864,7 @@ async function insertLog(params, from) {
return result;
} catch(error) {
console.error("insertLog err:", error);
return { message: 'insertLog_failed', error: error };
} finally {
client.release();
}
@@ -2328,42 +2345,54 @@ exports.checkTargetExists = async (req, res) => {
}
exports.createFolder = async (req, res) => {
const projectId = req.baseUrl.split('/')[1];
try {
const projectId = req.baseUrl.split('/')[1];
let { params } = req.body;
params.projectId = projectId;
let { params } = req.body;
params.projectId = projectId;
let activity = 'createFolder';
let folderType = params.folderType;
if (folderType) activity = `${activity}-${folderType}`;
params.activity = activity;
let activity = 'createFolder';
let folderType = params.folderType;
if (folderType) activity = `${activity}-${folderType}`;
params.activity = activity;
let insertDataResult = await insertData(params);
if (insertDataResult.message == 'insertData_success') {
let dataIdArr = [];
for (let i = 0; i < insertDataResult.rows.length; i++) {
let row = insertDataResult.rows[i];
dataIdArr.push(row.data_id);
}
params.dataIdArr = dataIdArr;
params.userIp = req.ip;
let insertLogResult = await insertLog(params);
if (insertLogResult.message == 'insertLog_success') {
let resultData = {
message: 'createFolder_success',
projectId: projectId,
activity: activity,
resourcePath: params.resourcePathArr[0]
};
let io = getIo();
io.emit('createFolder_success', resultData);
res.status(200).json({
message: 'createFolder_success',
});
let insertDataResult = await insertData(params);
if (insertDataResult && insertDataResult.message == 'insertData_success') {
let dataIdArr = [];
for (let i = 0; i < insertDataResult.rows.length; i++) {
let row = insertDataResult.rows[i];
dataIdArr.push(row.data_id);
}
params.dataIdArr = dataIdArr;
params.userIp = req.ip;
let insertLogResult = await insertLog(params);
if (insertLogResult && insertLogResult.message == 'insertLog_success') {
let resultData = {
message: 'createFolder_success',
projectId: projectId,
activity: activity,
resourcePath: params.resourcePathArr[0]
};
let io = getIo();
io.emit('createFolder_success', resultData);
return res.status(200).json({
message: 'createFolder_success',
});
}
}
res.status(500).json({
message: 'createFolder_failed',
error: '폴더 생성 중 오류가 발생했습니다.'
});
} catch (error) {
console.error("createFolder error:", error);
res.status(500).json({
message: 'createFolder_failed',
error: error.message
});
}
}
@@ -2847,55 +2876,62 @@ exports.relocateTarget = async(req, res) => {
}
exports.removeTarget = async(req, res) => {
let { params } = req.body;
let permission = JSON.parse(params.userInfoString).permission;
let depth = getDepth(params.resourcePathArr[0]);
let isRecycleBinModal = params.isRecycleBinModal;
try {
let { params } = req.body;
let permission = JSON.parse(params.userInfoString).permission;
let depth = getDepth(params.resourcePathArr[0]);
let isRecycleBinModal = params.isRecycleBinModal;
if (!isRecycleBinModal && (depth == 1 && permission < 191) || (depth >= 2 && permission < 7)) {
res.status(200).json({
message: 'removeTarget_failed_permission',
});
} else {
const projectId = req.baseUrl.split('/')[1];
params.projectId = projectId;
if (!isRecycleBinModal && (depth == 1 && permission < 191) || (depth >= 2 && permission < 7)) {
return res.status(200).json({
message: 'removeTarget_failed_permission',
});
} else {
const projectId = req.baseUrl.split('/')[1];
params.projectId = projectId;
// console.log('!!!!!!!!!!!!!!!!!!!!!! removeTarget');
// console.log(params);
let activity = `removeTarget_${params.dataType}`;
params.activity = activity;
let activity = `removeTarget_${params.dataType}`;
params.activity = activity;
let updateDataRemoveResult = await updateDataRemove(params);
if (updateDataRemoveResult && updateDataRemoveResult.message == 'updateDataRemove_success') {
params.userIp = req.ip;
let updateDataRemoveResult = await updateDataRemove(params);
if (updateDataRemoveResult.message == 'updateDataRemove_success') {
params.userIp = req.ip;
if (params.dataType == 'file') {
let updateLastFolderActDateResult = await updateLastFolderActDate(params.depth3DataIdArr);
}
if (params.dataType == 'file') {
let updateLastFolderActDateResult = await updateLastFolderActDate(params.depth3DataIdArr);
// if (updateLastFolderActDateResult.message == 'updateLastFolderActDate_success') {
// }
}
let insertLogResult = await insertLog(params);
if (insertLogResult.message == 'insertLog_success') {
let resultData = {
message: `removeTarget_success`,
projectId: projectId,
activity: activity,
resourcePathArr: params.resourcePathArr,
userInfoString: params.userInfoString,
isExpiredFolder: params.isExpiredFolder
};
let io = getIo();
io.emit('removeTarget_success', resultData);
res.status(200).json({
message: 'removeTarget_success',
});
let insertLogResult = await insertLog(params);
if (insertLogResult && insertLogResult.message == 'insertLog_success') {
let resultData = {
message: `removeTarget_success`,
projectId: projectId,
activity: activity,
resourcePathArr: params.resourcePathArr,
userInfoString: params.userInfoString,
isExpiredFolder: params.isExpiredFolder
};
let io = getIo();
io.emit('removeTarget_success', resultData);
return res.status(200).json({
message: 'removeTarget_success',
});
}
}
res.status(500).json({
message: 'removeTarget_failed',
error: '대상 제거 중 오류가 발생했습니다.'
});
}
} catch (error) {
console.error("removeTarget error:", error);
res.status(500).json({
message: 'removeTarget_failed',
error: error.message
});
}
}