한글뷰어 기능수정
This commit is contained in:
@@ -76,7 +76,7 @@ exports.getProjects = async (req, res) => {
|
||||
};
|
||||
|
||||
exports.createProject = async (req, res) => {
|
||||
const { project_id, project_nm, short_nm, category, limit_storage, is_active } = req.body;
|
||||
const { project_id, project_nm, short_nm, category, limit_storage, is_active, overview } = req.body;
|
||||
if (!project_id || !project_nm) {
|
||||
return res.status(400).json({ error: "프로젝트 ID와 명칭은 필수입니다." });
|
||||
}
|
||||
@@ -93,8 +93,8 @@ exports.createProject = async (req, res) => {
|
||||
const storage_byte = limit_storage ? parseInt(limit_storage) * 1024 * 1024 * 1024 : 0;
|
||||
|
||||
const query = `
|
||||
INSERT INTO ver4.${tbProject} (project_id, project_nm, short_nm, category, storage_byte, is_active, user_id, create_date)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, CURRENT_TIMESTAMP)
|
||||
INSERT INTO ver4.${tbProject} (project_id, project_nm, short_nm, category, storage_byte, is_active, user_id, overview, create_date)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, CURRENT_TIMESTAMP)
|
||||
RETURNING *;
|
||||
`;
|
||||
const result = await client.query(query, [
|
||||
@@ -104,13 +104,15 @@ exports.createProject = async (req, res) => {
|
||||
category || null,
|
||||
storage_byte,
|
||||
is_active ?? true,
|
||||
req.user?.user_id || 'admin'
|
||||
req.user?.user_id || 'admin',
|
||||
overview !== false // 기본값 true
|
||||
]);
|
||||
const userIp = req.headers['cf-connecting-ip'] || req.ip || req.headers['x-forwarded-for'] || req.connection?.remoteAddress;
|
||||
await insertAuditLog(project_id, 'createProject', req.user?.user_id, userIp, [
|
||||
`Project Name: ${project_nm}`,
|
||||
`Category: ${category}`,
|
||||
`Storage limit: ${limit_storage} GB`
|
||||
`Storage limit: ${limit_storage} GB`,
|
||||
`Overview enabled: ${overview !== false}`
|
||||
]);
|
||||
res.status(201).json(result.rows[0]);
|
||||
|
||||
@@ -124,18 +126,18 @@ exports.createProject = async (req, res) => {
|
||||
|
||||
exports.updateProject = async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const { project_nm, short_nm, category, limit_storage, is_active } = req.body;
|
||||
const { project_nm, short_nm, category, limit_storage, is_active, overview } = req.body;
|
||||
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
const storage_byte = limit_storage ? parseInt(limit_storage) * 1024 * 1024 * 1024 : 0;
|
||||
const query = `
|
||||
UPDATE ver4.${tbProject}
|
||||
SET project_nm = $1, short_nm = $2, category = $3, storage_byte = $4, is_active = $5
|
||||
WHERE project_id = $6
|
||||
SET project_nm = $1, short_nm = $2, category = $3, storage_byte = $4, is_active = $5, overview = $6
|
||||
WHERE project_id = $7
|
||||
RETURNING *;
|
||||
`;
|
||||
const result = await client.query(query, [project_nm, short_nm || null, category || null, storage_byte, is_active, id]);
|
||||
const result = await client.query(query, [project_nm, short_nm || null, category || null, storage_byte, is_active, overview, id]);
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ error: "대상을 찾을 수 없습니다." });
|
||||
}
|
||||
@@ -144,7 +146,8 @@ exports.updateProject = async (req, res) => {
|
||||
`Project Name: ${project_nm}`,
|
||||
`Category: ${category}`,
|
||||
`Storage limit: ${limit_storage} GB`,
|
||||
`Active status: ${is_active}`
|
||||
`Active status: ${is_active}`,
|
||||
`Overview enabled: ${overview}`
|
||||
]);
|
||||
res.status(200).json(result.rows[0]);
|
||||
} catch (err) {
|
||||
@@ -517,37 +520,62 @@ exports.createUser = async (req, res) => {
|
||||
|
||||
exports.updateUser = async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const { user_nm, company, dept, position, group, is_resigned } = req.body;
|
||||
const { user_nm, user_pw, company, dept, position, group, is_resigned } = req.body;
|
||||
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
const query = `
|
||||
UPDATE ver4.tb_user
|
||||
SET user_nm = $1, company = $2, dept = $3, position = $4, "group" = $5, is_resigned = $6
|
||||
WHERE user_id = $7
|
||||
RETURNING *;
|
||||
`;
|
||||
const result = await client.query(query, [
|
||||
user_nm,
|
||||
company || null,
|
||||
dept || null,
|
||||
position || null,
|
||||
group || null,
|
||||
is_resigned,
|
||||
id
|
||||
]);
|
||||
let result;
|
||||
if (user_pw && user_pw.trim() !== '') {
|
||||
const passwordHash = crypto.createHash('sha256').update(user_pw).digest('hex');
|
||||
const query = `
|
||||
UPDATE ver4.tb_user
|
||||
SET user_nm = $1, user_pw = $2, company = $3, dept = $4, position = $5, "group" = $6, is_resigned = $7
|
||||
WHERE user_id = $8
|
||||
RETURNING *;
|
||||
`;
|
||||
result = await client.query(query, [
|
||||
user_nm,
|
||||
passwordHash,
|
||||
company || null,
|
||||
dept || null,
|
||||
position || null,
|
||||
group || null,
|
||||
is_resigned,
|
||||
id
|
||||
]);
|
||||
} else {
|
||||
const query = `
|
||||
UPDATE ver4.tb_user
|
||||
SET user_nm = $1, company = $2, dept = $3, position = $4, "group" = $5, is_resigned = $6
|
||||
WHERE user_id = $7
|
||||
RETURNING *;
|
||||
`;
|
||||
result = await client.query(query, [
|
||||
user_nm,
|
||||
company || null,
|
||||
dept || null,
|
||||
position || null,
|
||||
group || null,
|
||||
is_resigned,
|
||||
id
|
||||
]);
|
||||
}
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ error: "대상을 찾을 수 없습니다." });
|
||||
}
|
||||
|
||||
const user = result.rows[0];
|
||||
const userIp = req.headers['cf-connecting-ip'] || req.ip || req.headers['x-forwarded-for'] || req.connection?.remoteAddress;
|
||||
await insertAuditLog('SYSTEM', 'updateUser', req.user?.user_id, userIp, [
|
||||
const details = [
|
||||
`Updated user_id: ${id}`,
|
||||
`User name: ${user_nm}`,
|
||||
`Group: ${group}`,
|
||||
`Is resigned: ${is_resigned}`
|
||||
]);
|
||||
];
|
||||
if (user_pw && user_pw.trim() !== '') {
|
||||
details.push("Password was updated");
|
||||
}
|
||||
await insertAuditLog('SYSTEM', 'updateUser', req.user?.user_id, userIp, details);
|
||||
user.user_pw = undefined;
|
||||
res.status(200).json(user);
|
||||
} catch (err) {
|
||||
@@ -671,6 +699,13 @@ exports.updateSystemPolicy = async (req, res) => {
|
||||
`Limit days: ${limit_days}`,
|
||||
`Is active: ${is_active}`
|
||||
]);
|
||||
try {
|
||||
const { getIo } = require('../../socket.js');
|
||||
const io = getIo();
|
||||
io.emit('updateSystemPolicy_success', result.rows[0]);
|
||||
} catch (socketErr) {
|
||||
console.error("Failed to emit updateSystemPolicy_success:", socketErr.message);
|
||||
}
|
||||
res.status(200).json(result.rows[0]);
|
||||
} catch (err) {
|
||||
console.error("updateSystemPolicy Error:", err);
|
||||
|
||||
@@ -2581,6 +2581,64 @@ exports.uploadData = async (req, res, next) => {
|
||||
let insertLogResult = await insertLog(params);
|
||||
if (insertLogResult.message == 'insertLog_success') {
|
||||
|
||||
// PPT/PPTX 파일 업로드 즉시 PDF 변환 처리
|
||||
for (let i = 0; i < insertDataResult.rows.length; i++) {
|
||||
try {
|
||||
let row = insertDataResult.rows[i];
|
||||
let resourcePath = params.resourcePathArr[i];
|
||||
let ext = (resourcePath.split('.').pop()).replace('.', '').toLowerCase();
|
||||
|
||||
if (['ppt', 'pptx'].includes(ext)) {
|
||||
let dataId = row.data_id;
|
||||
let objectKey = params.objectKeyArr[i];
|
||||
let storageType = params.storageType;
|
||||
let userInfoString = params.userInfoString;
|
||||
let userIp = req.ip;
|
||||
let bucket = projectId;
|
||||
|
||||
let command = new GetObjectCommand({
|
||||
Bucket: bucket,
|
||||
Key: objectKey,
|
||||
});
|
||||
|
||||
let url = await getSignedUrl(s3, command, { expiresIn: 60 * 60 * 6 }); // 유효시간 6시간
|
||||
|
||||
let initiator = `DEV_LOCAL_${JSON.parse(userInfoString).user_id}`;
|
||||
let type = 'archive';
|
||||
if (env == 'production') {
|
||||
if (deploymentType == 'ONPREMISE') initiator = 'HYHC_ONPREMISE';
|
||||
if (deploymentType == 'CLOUD') initiator = `AWS_CLOUD_${cloudType}`;
|
||||
}
|
||||
|
||||
const job = await convertPdfQueue.add(
|
||||
`'${initiator}'에서 문서를 PDF로 변환`,
|
||||
{ resourcePath, url, objectKey, bucket, storageType, dataId, projectId, userInfoString, userIp, initiator, type, serviceName }
|
||||
);
|
||||
|
||||
let resourcePathClean = resourcePath.startsWith('/') ? resourcePath.slice(1) : resourcePath;
|
||||
let pathArray = getPathArray(resourcePathClean);
|
||||
|
||||
convertingDataArr.push({
|
||||
dataId: dataId,
|
||||
resourcePath: resourcePath,
|
||||
depth1: pathArray[0],
|
||||
depth2: pathArray[1],
|
||||
depth3: pathArray[2],
|
||||
jobId: job.id
|
||||
});
|
||||
|
||||
// 변환 시작 소켓 전송
|
||||
let startEventData = { projectId: projectId, resourcePath: resourcePath, convertingDataArr: convertingDataArr };
|
||||
let io = getIo();
|
||||
io.emit('convert_start', startEventData);
|
||||
|
||||
console.log(`[PPT Auto-Convert] queued job ${job.id} for dataId ${dataId} (${resourcePath})`);
|
||||
}
|
||||
} catch (autoErr) {
|
||||
console.error(`[PPT Auto-Convert Failed] index ${i}:`, autoErr);
|
||||
}
|
||||
}
|
||||
|
||||
let resultData = {
|
||||
message: 'uploadData_success',
|
||||
projectId: projectId,
|
||||
@@ -2881,8 +2939,81 @@ exports.removeTarget = async(req, res) => {
|
||||
let permission = JSON.parse(params.userInfoString).permission;
|
||||
let depth = getDepth(params.resourcePathArr[0]);
|
||||
let isRecycleBinModal = params.isRecycleBinModal;
|
||||
const isExpiredFolder = params.isExpiredFolder === true;
|
||||
|
||||
if (!isRecycleBinModal && (depth == 1 && permission < 191) || (depth >= 2 && permission < 7)) {
|
||||
if (isExpiredFolder) {
|
||||
// 자동 기한 만료 삭제의 경우 안전 검증
|
||||
if (depth !== 3 || params.dataType !== 'folder') {
|
||||
return res.status(400).json({
|
||||
message: 'removeTarget_failed',
|
||||
error: '보존 정책 자동 삭제는 3단계 폴더만 대상이 될 수 있습니다.'
|
||||
});
|
||||
}
|
||||
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
// 1. 최신 시스템 정책 조회
|
||||
const policyRes = await client.query("SELECT * FROM ver4.tb_system_policy WHERE policy_key = 'GLOBAL_DELETE_POLICY'");
|
||||
if (policyRes.rows.length === 0 || !policyRes.rows[0].is_active) {
|
||||
return res.status(400).json({
|
||||
message: 'removeTarget_failed',
|
||||
error: '자동 삭제 정책이 비활성화 상태입니다.'
|
||||
});
|
||||
}
|
||||
const { limit_file_count, limit_days } = policyRes.rows[0];
|
||||
|
||||
// 2. 해당 폴더의 실제 정보 조회 (최종 활동 시각 확인)
|
||||
const resourcePath = params.resourcePathArr[0];
|
||||
const projectId = req.baseUrl.split('/')[1];
|
||||
const folderQuery = `
|
||||
SELECT last_folder_act_date
|
||||
FROM ver4.${tbData}
|
||||
WHERE project_id = $1 AND is_folder = true AND data_depth = 3 AND is_removed = false
|
||||
AND path1 = $2 AND path2 = $3 AND path3 = $4;
|
||||
`;
|
||||
const folderRes = await client.query(folderQuery, [
|
||||
projectId,
|
||||
getPathSegment(resourcePath, 1),
|
||||
getPathSegment(resourcePath, 2),
|
||||
getPathSegment(resourcePath, 3)
|
||||
]);
|
||||
|
||||
if (folderRes.rows.length === 0) {
|
||||
return res.status(404).json({
|
||||
message: 'removeTarget_failed',
|
||||
error: '해당 폴더를 찾을 수 없거나 이미 삭제되었습니다.'
|
||||
});
|
||||
}
|
||||
|
||||
const lastFolderActDate = new Date(folderRes.rows[0].last_folder_act_date);
|
||||
const expiryDate = new Date(lastFolderActDate.getTime() + limit_days * 24 * 60 * 60 * 1000);
|
||||
if (expiryDate > new Date()) {
|
||||
return res.status(400).json({
|
||||
message: 'removeTarget_failed',
|
||||
error: `해당 폴더는 아직 만료 기한이 지나지 않았습니다. (남은 기한 검증 실패)`
|
||||
});
|
||||
}
|
||||
|
||||
// 3. 하위 파일 개수 계산
|
||||
const filesCount = await getFilesCount(projectId, params.storageType || 'ONPREMISE', resourcePath);
|
||||
if (Number(filesCount) >= limit_file_count) {
|
||||
return res.status(400).json({
|
||||
message: 'removeTarget_failed',
|
||||
error: `해당 폴더의 파일 개수가 기준(${limit_file_count}개) 이상입니다. (파일 개수 검증 실패)`
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("isExpiredFolder verification error:", err);
|
||||
return res.status(500).json({
|
||||
message: 'removeTarget_failed',
|
||||
error: '만료 폴더 검증 처리 중 오류가 발생했습니다.'
|
||||
});
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
}
|
||||
|
||||
if (!isExpiredFolder && !isRecycleBinModal && ((depth == 1 && permission < 191) || (depth >= 2 && permission < 7))) {
|
||||
return res.status(200).json({
|
||||
message: 'removeTarget_failed_permission',
|
||||
});
|
||||
@@ -3221,7 +3352,7 @@ exports.addConvetPdfLog = async(req, res) => {
|
||||
exports.removeConvertingData = async(req, res) => {
|
||||
const projectId = req.baseUrl.split('/')[1];
|
||||
let { params } = req.body;
|
||||
let { resourcePath, dataId, userInfoString } = params;
|
||||
let { resourcePath, dataId, userInfoString, stdout } = params;
|
||||
|
||||
//// 배열에서 파일 정보 삭제
|
||||
convertingDataArr = convertingDataArr.filter(data => data.dataId !== dataId);
|
||||
@@ -3238,7 +3369,8 @@ exports.removeConvertingData = async(req, res) => {
|
||||
convertingDataArr: convertingDataArr,
|
||||
resourcePath: resourcePath,
|
||||
dataId: dataId,
|
||||
userInfoString: userInfoString
|
||||
userInfoString: userInfoString,
|
||||
stdout: stdout || ''
|
||||
};
|
||||
|
||||
let io = getIo();
|
||||
@@ -4363,6 +4495,7 @@ async function updateThumbnailInfoAction(projectId, params) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 삭제예정(2025.10.31): 이호성
|
||||
// exports.get3dViewerThumbUrl = async(req, res, next) => {
|
||||
// const dataId = req.query.dataId;
|
||||
|
||||
Reference in New Issue
Block a user