4403 lines
156 KiB
JavaScript
4403 lines
156 KiB
JavaScript
const path = require('path');
|
|
const pool = require("../db/pool.js");
|
|
const multer = require('multer');
|
|
const archiver = require('archiver');
|
|
const util = require('util');
|
|
const { exec, execSync } = require('child_process');
|
|
const execPromise = util.promisify(exec);
|
|
const { getIo } = require('../socket.js');
|
|
// const { userInfo } = require('os');
|
|
// const { query } = require('winston');
|
|
|
|
// const { GoogleGenerativeAI } = require("@google/genai");
|
|
const pdfParse = require('pdf-parse');
|
|
const { encode } = require('gpt-tokenizer');
|
|
const dotenv = require('dotenv');
|
|
dotenv.config();
|
|
|
|
//// s3 api 관련
|
|
const { getSignedUrl } = require('@aws-sdk/s3-request-presigner');
|
|
const {
|
|
ListObjectsV2Command,
|
|
DeleteObjectCommand,
|
|
CopyObjectCommand,
|
|
HeadObjectCommand,
|
|
PutObjectCommand,
|
|
GetObjectCommand,
|
|
ListBucketsCommand
|
|
} = require('@aws-sdk/client-s3');
|
|
|
|
const serviceName = process.env.SERVICE_NAME;
|
|
|
|
//// env의 DEPLOYMENT_TYPE에 따라 클라이언트 설정
|
|
const onPremiseClient = require('../config/onPremiseClient.js');
|
|
const cloudClient = require('../config/cloudClient.js');
|
|
const storageClients = {
|
|
'ONPREMISE': onPremiseClient,
|
|
'CLOUD': cloudClient
|
|
}
|
|
const deploymentType = process.env.DEPLOYMENT_TYPE;
|
|
const s3 = storageClients[deploymentType];
|
|
|
|
const cloudType = process.env.CLOUD_TYPE;
|
|
|
|
//// env의 NODE_ENV에 따라 DB 테이블 이름 설정
|
|
const env = process.env.NODE_ENV;
|
|
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';
|
|
// const tbData = env == 'production'? 'tb_data':'tb_data';
|
|
// const tbClickLog = env == 'production'? 'tb_click_log':'tb_click_log';
|
|
|
|
//// 큐 관련
|
|
const { redisConnection } = require('../config/redis.js');
|
|
const {
|
|
convertPdfQueue,
|
|
zipFolderQueue,
|
|
thumbQueue,
|
|
postProcessVideoQueue,
|
|
summarizeAIQueue,
|
|
summarizeAPIQueue
|
|
} = require('../queue.js'); // queue.js에서 행동별 Queue 객체 생성 후 사용
|
|
const { application } = require('express');
|
|
|
|
//// 변환 필요 확장자, 변환 불필요 확장자, 지원여부 확장자
|
|
// -> pdf 암호화 안하는 버전 - 변환 불필요 확장자에 pdf 포함
|
|
let needConvertExtArr = ['hwp', 'hwpx', 'doc', 'docx', 'xls', 'xlsx', 'xlsm', 'ppt', 'pptx', 'dwg', 'dxf', 'grm'];
|
|
let notNeedConvertExtArr = ['pdf', 'gsim', 'ifc', 'png', 'jpg', 'jpeg', 'webp', 'gif', 'mp4', 'mov', 'webm', 'txt', 'log', 'md', 'url', 'zip', 'glb', 'gltf', 'obj', 'stl', 'fbx', '3dm', 'html'];
|
|
// -> pdf 암호화 하는 버전 - 변환 필요 확장자에 pdf 포함
|
|
// let needConvertExtArr = ['pdf', 'hwp', 'hwpx', 'doc', 'docx', 'xls', 'xlsx', 'xlsm', 'ppt', 'pptx', 'dwg', 'dxf'];
|
|
// let notNeedConvertExtArr = ['mp4', 'jpg', 'jpeg', 'png'];
|
|
let supportedExtArr = [...needConvertExtArr, ...notNeedConvertExtArr];
|
|
|
|
//// 현재 변환중인 파일 정보를 저장하는 배열
|
|
let convertingDataArr = [];
|
|
//// 현재 AI 요약중인 파일 정보를 저장하는 배열
|
|
let summarizeAiDataArr = [];
|
|
|
|
// 🔻🔻🔻🔻🔻🔻🔻🔻 유틸리티 함수 시작 🔻🔻🔻🔻🔻🔻🔻🔻
|
|
|
|
// storageType 에 따라 클라이언트 리턴
|
|
function getS3(storageType) {
|
|
return storageType === 'Cloud'
|
|
? cloudClient
|
|
: onPremiseClient; // 기본은 minIO
|
|
}
|
|
|
|
function getBasePrefix(pageType) {
|
|
return {
|
|
origin: `${pageType}/origin/`,
|
|
pdf: `${pageType}/pdf/`,
|
|
pdf_thumb: `${pageType}/pdf_thumb/`,
|
|
thumbnail: `${pageType}/thumbnail/`,
|
|
}
|
|
}
|
|
|
|
function getDepth(path) {
|
|
path = path.startsWith('/') ? path.slice(1) : path;
|
|
let pathSplit = path.split('/');
|
|
let result = pathSplit.length;
|
|
return result;
|
|
}
|
|
|
|
function getPathArray(path) {
|
|
let result = Array(8).fill(null);
|
|
let pathSplit = path.replace(/\/$/, '').split('/');
|
|
for(let i = 0; i < pathSplit.length; i++) {
|
|
result[i] = pathSplit[i];
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function getPathSegment(path, num) {
|
|
path = path.startsWith('/') ? path.slice(1) : path;
|
|
let pathSplit = path.split('/');
|
|
return pathSplit[num-1];
|
|
}
|
|
|
|
function makeObjectKeyTimestamp(date) {
|
|
if (date) date = new Date(date);
|
|
else date = new Date(Date.now());
|
|
|
|
// Intl API로 현재 시스템 타임존 확인
|
|
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
|
|
// KST 변환이 필요한 경우에만 UTC + 9시간 적용
|
|
const isUtcOrNonSeoul = !timeZone || timeZone !== 'Asia/Seoul';
|
|
date = isUtcOrNonSeoul ? new Date(date.getTime() + 9 * 60 * 60 * 1000) : date;
|
|
|
|
let YY = String(date.getFullYear()).substring(2, 4);
|
|
let MM = String(date.getMonth() + 1).padStart(2, '0');
|
|
let DD = String(date.getDate()).padStart(2, '0');
|
|
let HH = String(date.getHours()).padStart(2, '0');
|
|
let mm = String(date.getMinutes()).padStart(2, '0');
|
|
let ss = String(date.getSeconds()).padStart(2, '0');
|
|
let SSS = String(date.getMilliseconds()).padStart(3, '0');
|
|
|
|
return `${YY}${MM}${DD}-${HH}${mm}${ss}-${SSS}`;
|
|
}
|
|
|
|
function makePostgresTimestamp(date) {
|
|
if (date) date = new Date(date);
|
|
else date = new Date(Date.now());
|
|
|
|
// Intl API로 현재 시스템 타임존 확인
|
|
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
|
|
// KST 변환이 필요한 경우에만 UTC + 9시간 적용
|
|
const isUtcOrNonSeoul = !timeZone || timeZone !== 'Asia/Seoul';
|
|
date = isUtcOrNonSeoul ? new Date(date.getTime() + 9 * 60 * 60 * 1000) : date;
|
|
|
|
// console.log('@@@@@@@@@@@@@@');
|
|
// console.log(date);
|
|
// console.log(timeZone);
|
|
// console.log(isUtcOrNonSeoul);
|
|
// console.log(date);
|
|
|
|
let YYYY = date.getFullYear();
|
|
let MM = String(date.getMonth() + 1).padStart(2, '0');
|
|
let DD = String(date.getDate()).padStart(2, '0');
|
|
let HH = String(date.getHours()).padStart(2, '0');
|
|
let mm = String(date.getMinutes()).padStart(2, '0');
|
|
let ss = String(date.getSeconds()).padStart(2, '0');
|
|
let SSS = String(date.getMilliseconds()).padStart(3, '0');
|
|
|
|
return `${YYYY}-${MM}-${DD} ${HH}:${mm}:${ss}.${SSS}`;
|
|
}
|
|
|
|
function getFileNameFromKey(key) {
|
|
let keySplit1 = key?.split('/')[key.split('/').length-1];
|
|
let keySplit2 = keySplit1?.split('__')[0];
|
|
let keySplit3 = keySplit2?.split('.');
|
|
keySplit3?.pop();
|
|
let fileName = keySplit3?.join('.');
|
|
return fileName;
|
|
}
|
|
|
|
function getPermissionString(permission) {
|
|
let strings = {
|
|
1: '참관자',
|
|
// 2: '',
|
|
4: '일반참여자',
|
|
8: '보안참여자',
|
|
0: '부관리자',
|
|
}
|
|
|
|
return strings[`${permission}`];
|
|
}
|
|
|
|
// 🔺🔺🔺🔺🔺🔺🔺🔺 유틸리티 함수 끝 🔺🔺🔺🔺🔺🔺🔺🔺
|
|
|
|
|
|
|
|
// 🔻🔻🔻🔻🔻🔻🔻🔻 DB 관련 함수 시작 🔻🔻🔻🔻🔻🔻🔻🔻
|
|
|
|
async function selectData(projectId, storageType, userInfo, resourcePath) {
|
|
let userId = (userInfo)?userInfo.user_id:undefined;
|
|
if (!userId) return;
|
|
let permission = (userInfo)?userInfo.permission:undefined;
|
|
let depth = getDepth(resourcePath);
|
|
|
|
const client = await pool.connect();
|
|
try {
|
|
////////////////////////////////////////////
|
|
//// 민홍이형 파라미터 바인딩 작업
|
|
|
|
let values = [];
|
|
let paramCounter = 1;
|
|
|
|
// let queryString = `
|
|
// SELECT
|
|
// d.data_id, d.project_id, d.user_id, d.create_date, d.data_permission, d.bucket, d.is_folder, d.is_removed, d.data_depth,
|
|
// d.ext, d.path1, d.path2, d.path3, d.path4, d.path5, d.path6, d.path7, d.path8, d.mod_date, d.mod_user_id, d.mod_activity,
|
|
// d.data_size, d.memo, d.storage_type, d.object_key, d.preview_key, d.popup_key, d.ver, d.folder_type, d.thumbnail_key,
|
|
// d.lon, d.lat, d.height, d.author_id, d.author_nm,
|
|
// CASE
|
|
// WHEN u.is_resigned = TRUE THEN u.user_nm || '(퇴사자)'
|
|
// ELSE u.user_nm
|
|
// END AS user_nm,
|
|
// u.company, u.dept, u.position, u.user_pw, u.group, u.bookmark
|
|
// FROM ver4.${tbData} d
|
|
// INNER JOIN ver4.tb_user u
|
|
// ON d.user_id = u.user_id
|
|
// WHERE d.project_id = $${paramCounter++}
|
|
// AND d.bucket = $${paramCounter++}
|
|
// AND d.storage_type = $${paramCounter++}
|
|
// AND d.is_removed = false`;
|
|
|
|
let queryString = `
|
|
SELECT
|
|
d.data_id, d.project_id, d.user_id, d.create_date, d.data_permission, d.bucket, d.is_folder, d.is_removed, d.data_depth,
|
|
d.ext, d.path1, d.path2, d.path3, d.path4, d.path5, d.path6, d.path7, d.path8, d.mod_date, d.mod_user_id, d.mod_activity,
|
|
d.data_size, d.memo, d.storage_type, d.object_key, d.preview_key, d.popup_key, d.ver, d.folder_type, d.thumbnail_key,
|
|
d.lon, d.lat, d.height, d.author_id, d.author_nm, d.last_folder_act_date,
|
|
CASE
|
|
WHEN u.is_resigned = TRUE THEN u.user_nm || '(퇴사자)'
|
|
ELSE u.user_nm
|
|
END AS user_nm,
|
|
u.company, u.dept, u.position, u.user_pw, u.group, u.bookmark,
|
|
CASE
|
|
WHEN mod_u.is_resigned = TRUE THEN mod_u.user_nm || '(퇴사자)'
|
|
ELSE mod_u.user_nm
|
|
END AS mod_user_nm
|
|
FROM ver4.${tbData} d
|
|
INNER JOIN ver4.tb_user u
|
|
ON d.user_id = u.user_id
|
|
LEFT JOIN ver4.tb_user mod_u
|
|
ON d.mod_user_id = mod_u.user_id
|
|
WHERE d.project_id = $${paramCounter++}
|
|
AND d.bucket = $${paramCounter++}
|
|
AND d.storage_type = $${paramCounter++}
|
|
AND d.is_removed = false`;
|
|
|
|
values.push(projectId);
|
|
values.push(projectId);
|
|
values.push(storageType);
|
|
|
|
if (resourcePath == '') {
|
|
// resourcePath가 ''인 경우 -> 헤더 버튼에 표시할 depth가 1인 폴더만 조회
|
|
queryString += `
|
|
AND d.data_depth = $${paramCounter++}`;
|
|
values.push(depth);
|
|
} else {
|
|
// resourcePath가 ''이 아닌 경우 -> 해당 resourcePath의 아래 depth 폴더를 조회해야 하므로 depth에 1을 더해서 사용
|
|
queryString += `
|
|
AND d.data_depth = $${paramCounter++}`;
|
|
values.push(depth + 1);
|
|
|
|
if (depth + 1 >= 2) {
|
|
for (let i = 0; i < depth; i++) {
|
|
let num = i + 1;
|
|
queryString += `
|
|
AND d.path${num} = $${paramCounter++}`;
|
|
values.push(getPathSegment(resourcePath, num));
|
|
}
|
|
}
|
|
}
|
|
|
|
queryString += `
|
|
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;`;
|
|
|
|
////////////////////////////////////////////
|
|
//// chat gpt 중복 row 필터
|
|
// let values = [];
|
|
// let paramCounter = 1;
|
|
|
|
// let innerQuery = `
|
|
// SELECT
|
|
// d.data_id, d.project_id, d.user_id, d.create_date, d.data_permission, d.bucket, d.is_folder, d.is_removed, d.data_depth,
|
|
// d.ext, d.path1, d.path2, d.path3, d.path4, d.path5, d.path6, d.path7, d.path8, d.mod_date, d.mod_user_id, d.mod_activity,
|
|
// d.data_size, d.memo, d.storage_type, d.object_key, d.preview_key, d.popup_key, d.ver,
|
|
// u.user_nm, u.company, u.dept, u.position, u.user_pw, u.group, u.bookmark,
|
|
// ROW_NUMBER() OVER (
|
|
// PARTITION BY d.path1, d.path2, d.path3, d.path4, d.path5, d.path6, d.path7, d.path8
|
|
// ORDER BY d.mod_date DESC
|
|
// ) AS row_num
|
|
// FROM ver4.${tbData} d
|
|
// INNER JOIN ver4.tb_user u
|
|
// ON d.user_id = u.user_id
|
|
// WHERE d.project_id = $${paramCounter++}
|
|
// AND d.bucket = $${paramCounter++}
|
|
// AND d.storage_type = $${paramCounter++}
|
|
// AND d.is_removed = false`;
|
|
|
|
// values.push(projectId);
|
|
// values.push(projectId); // bucket이 projectId로 들어오는 구조라고 가정
|
|
// values.push(storageType);
|
|
|
|
// if (resourcePath == '') {
|
|
// innerQuery += `
|
|
// AND d.data_depth = $${paramCounter++}`;
|
|
// values.push(depth);
|
|
// } else {
|
|
// innerQuery += `
|
|
// AND d.data_depth = $${paramCounter++}`;
|
|
// values.push(depth + 1);
|
|
|
|
// if (depth + 1 >= 2) {
|
|
// for (let i = 0; i < depth; i++) {
|
|
// let num = i + 1;
|
|
// innerQuery += `
|
|
// AND d.path${num} = $${paramCounter++}`;
|
|
// values.push(getPathSegment(resourcePath, num));
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// // 권한 조건
|
|
// innerQuery += `
|
|
// AND ((d.data_permission + 32) & `;
|
|
|
|
// if (permission) {
|
|
// innerQuery += `$${paramCounter++}`;
|
|
// values.push(permission);
|
|
// } else {
|
|
// innerQuery += `(SELECT lev FROM ver4.${tbPermission} WHERE project_id = $${paramCounter++} AND user_id = $${paramCounter++})`;
|
|
// values.push(projectId);
|
|
// values.push(userId);
|
|
// }
|
|
// innerQuery += `) <> 0`;
|
|
|
|
|
|
// // 최종적으로 row_num = 1만 필터링
|
|
// let queryString = `
|
|
// SELECT * FROM (
|
|
// ${innerQuery}
|
|
// ) AS sub
|
|
// WHERE sub.row_num = 1
|
|
// ORDER BY sub.path1, sub.path2, sub.path3, sub.path4, sub.path5, sub.path6, sub.path7, sub.path8;
|
|
// `;
|
|
|
|
////////////////////////////////////////////
|
|
|
|
// console.log('==============');
|
|
// console.log(queryString);
|
|
// console.log('Values:', values);
|
|
|
|
let result = await client.query(queryString, values);
|
|
|
|
// console.log('@@@@');
|
|
// console.log(result.rows);
|
|
|
|
return result.rows;
|
|
} catch(error) {
|
|
console.error("selectData err:", error);
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
async function selectRemovedData(projectId, storageType, userInfo) {
|
|
let userId = (userInfo)?userInfo.user_id:undefined;
|
|
if (!userId) return;
|
|
let permission = (userInfo)?userInfo.permission:undefined;
|
|
|
|
const client = await pool.connect();
|
|
try {
|
|
let values = [];
|
|
let paramCounter = 1;
|
|
|
|
let queryString = `
|
|
SELECT
|
|
d.data_id, d.project_id, d.user_id, d.create_date, d.data_permission, d.bucket, d.is_folder, d.is_removed, d.data_depth,
|
|
d.ext, d.path1, d.path2, d.path3, d.path4, d.path5, d.path6, d.path7, d.path8, d.mod_date, d.mod_user_id, d.mod_activity,
|
|
d.data_size, d.memo, d.storage_type, d.object_key, d.preview_key, d.popup_key, d.ver, d.folder_type, d.thumbnail_key,
|
|
d.lon, d.lat, d.height, d.author_id, d.author_nm,
|
|
CASE
|
|
WHEN u.is_resigned = TRUE THEN u.user_nm || '(퇴사자)'
|
|
ELSE u.user_nm
|
|
END AS user_nm,
|
|
u.company, u.dept, u.position, u.user_pw, u.group, u.bookmark,
|
|
CASE
|
|
WHEN mod_u.is_resigned = TRUE THEN mod_u.user_nm || '(퇴사자)'
|
|
ELSE mod_u.user_nm
|
|
END AS mod_user_nm
|
|
FROM ver4.${tbData} d
|
|
INNER JOIN ver4.tb_user u
|
|
ON d.user_id = u.user_id
|
|
LEFT JOIN ver4.tb_user mod_u
|
|
ON d.mod_user_id = mod_u.user_id
|
|
WHERE d.project_id = $${paramCounter++}
|
|
AND d.bucket = $${paramCounter++}
|
|
AND d.storage_type = $${paramCounter++}
|
|
AND d.is_removed = true
|
|
AND d.is_folder = false`;
|
|
|
|
values.push(projectId);
|
|
values.push(projectId);
|
|
values.push(storageType);
|
|
|
|
queryString += `
|
|
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;`;
|
|
|
|
// console.log('==================');
|
|
// console.log(queryString);
|
|
// console.log(values);
|
|
|
|
let result = await client.query(queryString, values);
|
|
|
|
return result.rows;
|
|
} catch(error) {
|
|
console.error("selectRemovedData err:", error);
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
async function selectLog(projectId, params) {
|
|
const client = await pool.connect();
|
|
try {
|
|
let queryString = `
|
|
SELECT
|
|
l.log_id, l.project_id, l.activity, l.user_id, l.user_ip, l.log_date, l.path_arr, l.data_id_arr,
|
|
u.company, u.dept, u.position, u.user_pw, u.group, u.bookmark, u.user_nm
|
|
FROM ver4.${tbLog} l
|
|
INNER JOIN ver4.tb_user u
|
|
ON l.user_id = u.user_id
|
|
WHERE l.project_id = '${projectId}'`
|
|
|
|
if(params != undefined) {
|
|
|
|
// 특정 유저를 선택한 경우 removeTarget_folder_expired 제거
|
|
const isAllUser = params.user && params.user.length > 1;
|
|
|
|
let activityArr = params.activity ? [...params.activity] : null;
|
|
if (activityArr) {
|
|
if (!isAllUser) {
|
|
activityArr = activityArr.filter(a => a !== 'removeTarget_folder_expired');
|
|
}
|
|
|
|
if (activityArr.length > 0) {
|
|
const activityList = activityArr.map(a => `'${a}'`).join(',');
|
|
queryString += `
|
|
AND l.activity IN (${activityList})
|
|
`;
|
|
}
|
|
} else {
|
|
queryString += `
|
|
AND 1 = 0
|
|
`
|
|
}
|
|
|
|
if(params.user) {
|
|
|
|
let userArr = [...params.user];
|
|
|
|
// allUser인 경우 '-'도 포함
|
|
if (isAllUser) {
|
|
if (!userArr.includes('-')) {
|
|
userArr.push('-');
|
|
}
|
|
}
|
|
|
|
const userList = userArr.map(u => `'${u}'`).join(',');
|
|
queryString += `
|
|
AND l.user_id IN (${userList})
|
|
`;
|
|
|
|
}
|
|
|
|
if(params.startDate) {
|
|
queryString += `
|
|
AND l.log_date >= '${params.startDate} 00:00:00.000'
|
|
`
|
|
}
|
|
|
|
if(params.endDate) {
|
|
// 다음날 자정까지
|
|
queryString += `
|
|
AND l.log_date <= '${params.endDate} 23:59:59.999'
|
|
`
|
|
}
|
|
} else {
|
|
let today = new Date();
|
|
let weekAgo = new Date(today);
|
|
weekAgo.setDate(today.getDate() - 7);
|
|
|
|
// YYYY-MM-DD 형식
|
|
const toDateStr = (d) => {
|
|
const year = d.getFullYear();
|
|
const month = String(d.getMonth() + 1).padStart(2, '0');
|
|
const day = String(d.getDate()).padStart(2, '0');
|
|
return `${year}-${month}-${day}`;
|
|
};
|
|
|
|
let startDate = toDateStr(weekAgo);
|
|
let endDate = toDateStr(today);
|
|
|
|
// let today = makePostgresTimestamp(todayEnd);
|
|
// let weekAgo = makePostgresTimestamp(weekAgoStart);
|
|
|
|
queryString += `
|
|
AND l.log_date >= '${startDate} 00:00:00.000'
|
|
AND l.log_date <= '${endDate} 23:59:59.999'
|
|
`
|
|
}
|
|
|
|
queryString += `
|
|
ORDER BY l.log_id DESC
|
|
LIMIT 500;
|
|
`;
|
|
|
|
// console.log('============');
|
|
// console.log(queryString);
|
|
|
|
let result = await client.query(queryString);
|
|
|
|
// console.log('@@@@');
|
|
// console.log(result.rows);
|
|
// console.log(result.rows.length);
|
|
|
|
let logData = {};
|
|
result.rows.forEach(row => {
|
|
logData[row.log_id] = row;
|
|
})
|
|
return logData;
|
|
|
|
} catch(error) {
|
|
console.error("selectLog err:", error);
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
async function selectUserLog(projectId) {
|
|
const client = await pool.connect();
|
|
try {
|
|
let queryString = `
|
|
SELECT
|
|
l.log_id, l.project_id, l.activity, l.user_id, l.user_ip, l.log_date, l.path_arr, l.data_id_arr,
|
|
u.company, u.dept, u.position, u.user_pw, u.group, u.bookmark, u.user_nm
|
|
FROM ver4.${tbLog} l
|
|
INNER JOIN ver4.tb_user u
|
|
ON l.user_id = u.user_id
|
|
WHERE l.project_id = '${projectId}'
|
|
ORDER BY l.log_id DESC
|
|
`
|
|
|
|
// console.log('============');
|
|
// console.log(queryString);
|
|
|
|
let result = await client.query(queryString);
|
|
|
|
// console.log('@@@@');
|
|
// console.log(result.rows);
|
|
// console.log(result.rows.length);
|
|
|
|
let logData = {};
|
|
result.rows.forEach(row => {
|
|
logData[row.log_id] = row;
|
|
})
|
|
return logData;
|
|
|
|
} catch(error) {
|
|
console.error("selectUserLog err:", error);
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
// async function selectLog(projectId) {
|
|
// const client = await pool.connect();
|
|
// try {
|
|
// let queryString = `
|
|
// SELECT
|
|
// l.log_id, l.project_id, l.activity, l.user_id, l.user_ip, l.log_date, l.path_arr, l.data_id_arr,
|
|
// CASE
|
|
// WHEN u.is_resigned = TRUE THEN u.user_nm || '(퇴사자)'
|
|
// ELSE u.user_nm
|
|
// END AS user_nm,
|
|
// u.company, u.dept, u.position, u.user_pw, u.group, u.bookmark
|
|
// FROM ver4.${tbLog} l
|
|
// INNER JOIN ver4.tb_user u
|
|
// ON l.user_id = u.user_id
|
|
// WHERE l.project_id = '${projectId}'
|
|
// ORDER BY l.log_id DESC
|
|
// LIMIT 500;`;
|
|
|
|
// // console.log('============');
|
|
// // console.log(queryString);
|
|
|
|
// let result = await client.query(queryString);
|
|
|
|
// // console.log('@@@@');
|
|
// // console.log(result.rows);
|
|
|
|
// let logData = {};
|
|
// result.rows.forEach(row => {
|
|
// logData[row.log_id] = row;
|
|
// })
|
|
// return logData;
|
|
|
|
// } catch(error) {
|
|
// console.error("selectLog err:", error);
|
|
// } finally {
|
|
// client.release();
|
|
// }
|
|
// }
|
|
|
|
async function userLog(projectId) {
|
|
const client = await pool.connect();
|
|
try {
|
|
let queryString = `
|
|
SELECT
|
|
l.log_id, l.project_id, l.activity, l.user_id, l.user_ip, l.log_date, l.path_arr, l.data_id_arr,
|
|
CASE
|
|
WHEN u.is_resigned = TRUE THEN u.user_nm || '(퇴사자)'
|
|
ELSE u.user_nm
|
|
END AS user_nm,
|
|
u.company, u.dept, u.position, u.user_pw, u.group, u.bookmark
|
|
FROM ver4.${tbLog} l
|
|
INNER JOIN ver4.tb_user u
|
|
ON l.user_id = u.user_id
|
|
WHERE l.project_id = '${projectId}'
|
|
ORDER BY l.log_id DESC;
|
|
`;
|
|
|
|
let result = await client.query(queryString);
|
|
|
|
let logData = {};
|
|
result.rows.forEach(row => {
|
|
logData[row.log_id] = row;
|
|
})
|
|
return logData;
|
|
|
|
} catch(error) {
|
|
console.error("filterUserLog err:", error);
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
async function insertData(params) {
|
|
let {
|
|
projectId, userInfoString, storageType, dateArr, resourcePathArr, sizeArr, objectKeyArr,
|
|
thumbnailSizeArr, thumbnailKeyArr, coordArr, dataType, dataPermission, folderType, activity
|
|
} = params;
|
|
|
|
let bucket = projectId;
|
|
let userInfo = JSON.parse(userInfoString);
|
|
let userId = userInfo.user_id;
|
|
if (dataPermission == undefined) dataPermission = 1;
|
|
|
|
const client = await pool.connect();
|
|
try {
|
|
let isFolder = true;
|
|
if (dataType == 'file') isFolder = false;
|
|
|
|
let values = [];
|
|
let placeholders = [];
|
|
for (let i = 0; i < dateArr.length; i++) {
|
|
let createDate = makePostgresTimestamp(dateArr[i]);
|
|
|
|
let resourcePath;
|
|
if (resourcePathArr && resourcePathArr.length != 0) resourcePath = resourcePathArr[i];
|
|
resourcePath = resourcePath.startsWith('/') ? resourcePath.slice(1) : resourcePath;
|
|
|
|
let depth = getDepth(resourcePath);
|
|
|
|
let ext = null;
|
|
if (dataType == 'file') ext = (resourcePath.split('.').pop()).replace('.', '').toLowerCase();
|
|
|
|
let dataSize = 0;
|
|
if (sizeArr && sizeArr.length != 0) dataSize = sizeArr[i];
|
|
|
|
let objectKey = null;
|
|
if (objectKeyArr && objectKeyArr.length != 0) objectKey = objectKeyArr[i];
|
|
|
|
let previewKey = null, popupKey = null;
|
|
if (!needConvertExtArr.includes(ext)) {
|
|
previewKey = objectKey;
|
|
popupKey = objectKey;
|
|
}
|
|
// let previewKey = objectKey;
|
|
// let popupKey = objectKey;
|
|
|
|
let thumbnailSize = 0;
|
|
if (thumbnailSizeArr && thumbnailSizeArr.length != 0) thumbnailSize = thumbnailSizeArr[i];
|
|
|
|
let thumbnailKey = null;
|
|
if (thumbnailKeyArr && thumbnailKeyArr.length != 0) thumbnailKey = thumbnailKeyArr[i];
|
|
|
|
let lon = null, lat = null, height = null;
|
|
if (coordArr && coordArr.length != 0) {
|
|
lon = coordArr[i]?.lon;
|
|
lat = coordArr[i]?.lat;
|
|
height = coordArr[i]?.height;
|
|
}
|
|
|
|
let lastFolderActDate = null;
|
|
if (activity == 'createFolder' && depth == 3) lastFolderActDate = makePostgresTimestamp(Date.now());
|
|
|
|
values.push(
|
|
projectId, userId, createDate, dataPermission, bucket, isFolder, depth, ext, dataSize,
|
|
storageType, objectKey, previewKey, popupKey, folderType, thumbnailSize, thumbnailKey, lon, lat,
|
|
height, lastFolderActDate, ...getPathArray(resourcePath)
|
|
);
|
|
|
|
let idx = i * 28;
|
|
placeholders.push(`
|
|
($${idx+1}, $${idx+2}, $${idx+3}, $${idx+4}, $${idx+5}, $${idx+6}, $${idx+7}, $${idx+8}, $${idx+9},
|
|
$${idx+10}, $${idx+11}, $${idx+12}, $${idx+13}, $${idx+14}, $${idx+15}, $${idx+16}, $${idx+17}, $${idx+18},
|
|
$${idx+19}, $${idx+20}, $${idx+21}, $${idx+22}, $${idx+23}, $${idx+24}, $${idx+25}, $${idx+26}, $${idx+27}, $${idx+28})
|
|
`);
|
|
}
|
|
|
|
let queryString = `
|
|
INSERT INTO ver4.${tbData} (
|
|
project_id, user_id, create_date, data_permission, bucket, is_folder, data_depth, ext, data_size,
|
|
storage_type, object_key, preview_key, popup_key, folder_type, thumbnail_size, thumbnail_key, lon, lat,
|
|
height, last_folder_act_date, path1, path2, path3, path4, path5, path6, path7, path8
|
|
) VALUES ${placeholders.join(',')}
|
|
RETURNING data_id;
|
|
`;
|
|
|
|
// console.log('@@@@@@@@@@@@@@@@@');
|
|
// console.log(queryString);
|
|
// console.log('');
|
|
// console.log('!!!!!!!!!!!!!!!!!');
|
|
// console.log(values);
|
|
|
|
let { rows } = await client.query(queryString, values);
|
|
|
|
let result = { message: 'insertData_success', rows: rows };
|
|
return result;
|
|
} catch(error) {
|
|
console.error("insertData err:", error);
|
|
return { message: 'insertData_failed', error: error };
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
async function insertLog(params, from) {
|
|
// Synchronous database inserts for activity log are now handled asynchronously
|
|
// by the activityLogger middleware via BullMQ.
|
|
return { message: 'insertLog_success' };
|
|
}
|
|
|
|
async function insertClickLog(params) {
|
|
// Synchronous database inserts for click log are now handled asynchronously
|
|
// by the activityLogger middleware via BullMQ.
|
|
return { message: 'insertClickLog_success' };
|
|
}
|
|
|
|
/*
|
|
updateRows
|
|
이름변경
|
|
다른 폴더로 이동
|
|
휴지통에 버리기
|
|
등록자 변경?
|
|
권한 변경
|
|
*/
|
|
async function updateDataRename(params) {
|
|
let { projectId, userInfoString, storageType, resourcePath, activity, newName, oldName } = params;
|
|
let bucket = projectId;
|
|
let userInfo = JSON.parse(userInfoString);
|
|
let userId = userInfo.user_id;
|
|
let dateNow = makePostgresTimestamp(Date.now());
|
|
|
|
let depth = getDepth(resourcePath);
|
|
|
|
const client = await pool.connect();
|
|
try {
|
|
let segment1 = getPathSegment(resourcePath, depth);
|
|
let values = [];
|
|
let paramCounter = 1; // 파라미터 번호($1, $2, ...)
|
|
|
|
let queryString = `
|
|
UPDATE ver4.${tbData}
|
|
SET
|
|
path${depth} = REPLACE(path${depth}, $${paramCounter++}, $${paramCounter++}),
|
|
mod_date = CASE
|
|
WHEN path${depth} = $${paramCounter++} AND data_depth = $${paramCounter++} THEN $${paramCounter++}
|
|
ELSE mod_date
|
|
END,
|
|
mod_user_id = CASE
|
|
WHEN path${depth} = $${paramCounter++} AND data_depth = $${paramCounter++} THEN $${paramCounter++}
|
|
ELSE mod_user_id
|
|
END,
|
|
mod_activity = CASE
|
|
WHEN path${depth} = $${paramCounter++} AND data_depth = $${paramCounter++} THEN $${paramCounter++}
|
|
ELSE mod_activity
|
|
END
|
|
WHERE is_removed = false
|
|
AND project_id = $${paramCounter++}
|
|
AND bucket = $${paramCounter++}
|
|
AND storage_type = $${paramCounter++}`;
|
|
|
|
console.log(queryString);
|
|
|
|
values.push(oldName); // $1
|
|
values.push(newName); // $2
|
|
values.push(segment1); // $3 (mod_date, mod_user_id, mod_activity의 WHEN 절에서 사용)
|
|
values.push(depth); // $4 (mod_date, mod_user_id, mod_activity의 WHEN 절에서 data_depth와 비교)
|
|
values.push(dateNow); // $5
|
|
values.push(segment1); // $6 (segment1이 다시 사용되지만, 다른 위치이므로 새로운 파라미터 번호 할당)
|
|
values.push(depth); // $7 (depth가 다시 사용되지만, 다른 위치이므로 새로운 파라미터 번호 할당)
|
|
values.push(userId); // $8
|
|
values.push(segment1); // $9 (segment1이 다시 사용되지만, 다른 위치이므로 새로운 파라미터 번호 할당)
|
|
values.push(depth); // $10 (depth가 다시 사용되지만, 다른 위치이므로 새로운 파라미터 번호 할당)
|
|
values.push(activity); // $11
|
|
values.push(projectId); // $12
|
|
values.push(bucket); // $13
|
|
values.push(storageType); // $14
|
|
|
|
for (let i = 0; i < depth; i++) {
|
|
let num = i + 1;
|
|
let segment2 = getPathSegment(resourcePath, num);
|
|
|
|
if (num === 4) {
|
|
queryString += `
|
|
AND path${num} IN ($${paramCounter++}, $${paramCounter++}, $${paramCounter++})`;
|
|
values.push(segment2);
|
|
values.push(`${segment2}_version`);
|
|
values.push(`${segment2}_attachment`);
|
|
} else {
|
|
queryString += `
|
|
AND path${num} = $${paramCounter++}`;
|
|
values.push(segment2);
|
|
}
|
|
}
|
|
|
|
queryString += `;`;
|
|
|
|
// console.log('========================');
|
|
// console.log(queryString);
|
|
// console.log('Values:', values); // 생성된 파라미터 값
|
|
|
|
await client.query(queryString, values);
|
|
|
|
let result = { message: 'updateDataRename_success' };
|
|
return result;
|
|
} catch(error) {
|
|
console.error(`updateDataRename error:`, error);
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
async function updateDataAuthor(params) {
|
|
let { userInfoString, dataIdArr, newAuthorId, newAuthorNm, activity } = params;
|
|
let userInfo = JSON.parse(userInfoString);
|
|
let userId = userInfo.user_id;
|
|
let dateNow = makePostgresTimestamp(Date.now());
|
|
|
|
const client = await pool.connect();
|
|
try {
|
|
let placeholders = dataIdArr.map((_, index) => `$${index + 6}`).join(', ');
|
|
let queryString = `
|
|
UPDATE ver4.${tbData}
|
|
SET
|
|
author_id = $1,
|
|
author_nm = $2,
|
|
mod_date = $3,
|
|
mod_user_id = $4,
|
|
mod_activity = $5
|
|
WHERE is_removed = false
|
|
AND data_id IN (${placeholders});
|
|
`;
|
|
|
|
let values = [newAuthorId, newAuthorNm, dateNow, userId, activity, ...dataIdArr];
|
|
|
|
await client.query(queryString, values);
|
|
|
|
let result = { message: 'updateDataAuthor_success' };
|
|
return result;
|
|
} catch(error) {
|
|
console.error("updateDataAuthor err:", error);
|
|
}finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
async function updateDataRelocate(params) {
|
|
let { projectId, userInfoString, storageType, fromPathArr, toPathArr, dataIdArr, activity } = params;
|
|
let bucket = projectId;
|
|
let userInfo = JSON.parse(userInfoString);
|
|
let userId = userInfo.user_id;
|
|
let dateNow = makePostgresTimestamp(Date.now());
|
|
|
|
const getPathSegments = (path) => path.split('/').filter(Boolean);
|
|
|
|
const extractFileName = (path4) => {
|
|
const match = path4.match(/^(.*?)(?:_attachment|_version)?$/);
|
|
return match ? match[1] : path4;
|
|
};
|
|
|
|
const getNewSegment = (oldSegment, fromBase, toBase) => {
|
|
if (oldSegment === fromBase) return toBase;
|
|
if (oldSegment === `${fromBase}_attachment`) return `${toBase}_attachment`;
|
|
if (oldSegment === `${fromBase}_version`) return `${toBase}_version`;
|
|
return oldSegment;
|
|
};
|
|
|
|
const client = await pool.connect();
|
|
try {
|
|
await client.query('BEGIN');
|
|
|
|
for (let i = 0; i < fromPathArr.length; i++) {
|
|
let from = getPathSegments(fromPathArr[i]);
|
|
let to = getPathSegments(toPathArr[i]);
|
|
|
|
if (from.length == 5 && to.length == 4) {
|
|
// depth5 추가(버전/첨부) 파일을 depth4로 이동하는 경우
|
|
let dataId = dataIdArr[i];
|
|
for (let j = 0; j < 4; j++) {
|
|
to.push(null);
|
|
}
|
|
|
|
let query = `
|
|
UPDATE ver4.${tbData}
|
|
SET
|
|
data_depth = $1,
|
|
path1 = $2,
|
|
path2 = $3,
|
|
path3 = $4,
|
|
path4 = $5,
|
|
path5 = $6,
|
|
path6 = $7,
|
|
path7 = $8,
|
|
path8 = $9,
|
|
mod_date = $10,
|
|
mod_user_id = $11,
|
|
mod_activity = $12
|
|
WHERE is_removed = false
|
|
AND project_id = $13
|
|
AND bucket = $14
|
|
AND storage_type = $15
|
|
AND data_id = $16
|
|
`;
|
|
|
|
let values = [4, ...to, dateNow, userId, activity, projectId, bucket, storageType, dataId];
|
|
|
|
await client.query(query, values);
|
|
} else {
|
|
// depth4 파일을 depth4 그대로 이동하는 경우
|
|
let fromBase = extractFileName(from[3]); // depth4
|
|
let toBase = extractFileName(to[3]); // depth4
|
|
|
|
let baseUpdate = async (path4, depth) => {
|
|
let fromPath = [...from];
|
|
let toPath = [...to];
|
|
|
|
// path4 치환
|
|
fromPath[3] = path4;
|
|
toPath[3] = getNewSegment(path4, fromBase, toBase);
|
|
|
|
let values = [
|
|
toPath[0], toPath[1], toPath[2], toPath[3],
|
|
path4, dateNow,
|
|
path4, userId,
|
|
path4, activity,
|
|
projectId, bucket, storageType
|
|
];
|
|
|
|
let query = `
|
|
UPDATE ver4.${tbData}
|
|
SET
|
|
path1 = $1,
|
|
path2 = $2,
|
|
path3 = $3,
|
|
path4 = $4,
|
|
mod_date = CASE WHEN path4 = $5 AND data_depth = ${depth} THEN $6 ELSE mod_date END,
|
|
mod_user_id = CASE WHEN path4 = $7 AND data_depth = ${depth} THEN $8 ELSE mod_user_id END,
|
|
mod_activity = CASE WHEN path4 = $9 AND data_depth = ${depth} THEN $10 ELSE mod_activity END
|
|
WHERE is_removed = false
|
|
AND project_id = $11
|
|
AND bucket = $12
|
|
AND storage_type = $13
|
|
AND path1 = $14
|
|
AND path2 = $15
|
|
AND path3 = $16
|
|
AND path4 = $17
|
|
`;
|
|
|
|
values.push(fromPath[0], fromPath[1], fromPath[2], fromPath[3]);
|
|
|
|
await client.query(query, values);
|
|
};
|
|
|
|
// depth4 원본 파일 이동
|
|
await baseUpdate(from[3], 4);
|
|
|
|
// depth4 _attachment 이동 (있을 경우만)
|
|
await baseUpdate(`${fromBase}_attachment`, 4);
|
|
|
|
// depth4 _version 이동 (있을 경우만)
|
|
await baseUpdate(`${fromBase}_version`, 4);
|
|
|
|
// depth5 첨부/버전 내부 파일 이동
|
|
let attachmentTypes = ['_attachment', '_version'];
|
|
for (let suffix of attachmentTypes) {
|
|
let original = `${fromBase}${suffix}`;
|
|
let renamed = `${toBase}${suffix}`;
|
|
|
|
let values = [
|
|
to[0], to[1], to[2], renamed,
|
|
original, dateNow,
|
|
original, userId,
|
|
original, activity,
|
|
projectId, bucket, storageType,
|
|
from[0], from[1], from[2], original
|
|
];
|
|
|
|
let query = `
|
|
UPDATE ver4.${tbData}
|
|
SET
|
|
path1 = $1,
|
|
path2 = $2,
|
|
path3 = $3,
|
|
path4 = $4,
|
|
path5 = path5,
|
|
mod_date = CASE WHEN path4 = $5 AND data_depth = 5 THEN $6 ELSE mod_date END,
|
|
mod_user_id = CASE WHEN path4 = $7 AND data_depth = 5 THEN $8 ELSE mod_user_id END,
|
|
mod_activity = CASE WHEN path4 = $9 AND data_depth = 5 THEN $10 ELSE mod_activity END
|
|
WHERE is_removed = false
|
|
AND project_id = $11
|
|
AND bucket = $12
|
|
AND storage_type = $13
|
|
AND path1 = $14
|
|
AND path2 = $15
|
|
AND path3 = $16
|
|
AND path4 = $17
|
|
`;
|
|
|
|
await client.query(query, values);
|
|
}
|
|
}
|
|
}
|
|
|
|
await client.query('COMMIT');
|
|
return { message: 'updateDataRelocate_success' };
|
|
} catch (err) {
|
|
await client.query('ROLLBACK');
|
|
console.error('updateDataRelocate error:', err);
|
|
throw err;
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
async function updateDataRemove(params) {
|
|
let { projectId, userInfoString, storageType, resourcePathArr, activity } = params;
|
|
let bucket = projectId;
|
|
let userInfo = JSON.parse(userInfoString);
|
|
let userId = userInfo.user_id;
|
|
let dateNow = makePostgresTimestamp(Date.now());
|
|
|
|
const client = await pool.connect();
|
|
try {
|
|
await client.query('BEGIN');
|
|
|
|
let values = [];
|
|
let paramCounter = 1;
|
|
let matchingConditionsArray = [];
|
|
|
|
for (const resourcePath of resourcePathArr) {
|
|
let depth = getDepth(resourcePath);
|
|
let condition = `data_depth >= $${paramCounter++}`;
|
|
values.push(depth);
|
|
|
|
for (let i = 0; i < depth; i++) {
|
|
let num = i + 1;
|
|
let segment = getPathSegment(resourcePath, num);
|
|
if (num === 4) {
|
|
condition += ` AND path${num} IN ($${paramCounter++}, $${paramCounter++}, $${paramCounter++})`;
|
|
values.push(segment);
|
|
values.push(`${segment}_version`);
|
|
values.push(`${segment}_attachment`);
|
|
} else {
|
|
condition += ` AND path${num} = $${paramCounter++}`;
|
|
values.push(segment);
|
|
}
|
|
}
|
|
|
|
matchingConditionsArray.push(`(${condition})`);
|
|
}
|
|
|
|
let matchingConditions = matchingConditionsArray.join(' OR ');
|
|
|
|
// UPDATE 쿼리 (RETURNING data_id)
|
|
const updateQueryString = `
|
|
UPDATE ver4.${tbData}
|
|
SET
|
|
is_removed = true,
|
|
mod_date = $${paramCounter++},
|
|
mod_user_id = $${paramCounter++},
|
|
mod_activity = $${paramCounter++}
|
|
WHERE data_id IN (
|
|
SELECT data_id
|
|
FROM ver4.${tbData}
|
|
WHERE is_removed = false
|
|
AND project_id = $${paramCounter++}
|
|
AND bucket = $${paramCounter++}
|
|
AND storage_type = $${paramCounter++}
|
|
AND (${matchingConditions})
|
|
)
|
|
RETURNING data_id;
|
|
`;
|
|
|
|
values.push(dateNow, userId, activity, projectId, bucket, storageType);
|
|
|
|
const updateResult = await client.query(updateQueryString, values);
|
|
const updatedDataIds = updateResult.rows.map(row => row.data_id);
|
|
|
|
if (updatedDataIds.length > 0) {
|
|
// DELETE 쿼리 (is_folder = true인 것만)
|
|
const deleteQueryString = `
|
|
DELETE FROM ver4.${tbData}
|
|
WHERE data_id = ANY($1) AND is_folder = true;
|
|
`;
|
|
await client.query(deleteQueryString, [updatedDataIds]);
|
|
}
|
|
|
|
await client.query('COMMIT');
|
|
return { message: 'updateDataRemove_success' };
|
|
} catch (error) {
|
|
await client.query('ROLLBACK');
|
|
console.error(`updateDataRemove error:`, error);
|
|
throw error;
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
// async function updateDataRemove(params) {
|
|
// let { projectId, userInfoString, storageType, resourcePathArr, activity } = params;
|
|
// let bucket = projectId;
|
|
// let userInfo = JSON.parse(userInfoString);
|
|
// let userId = userInfo.user_id;
|
|
// let dateNow = makePostgresTimestamp(Date.now());
|
|
|
|
// const client = await pool.connect();
|
|
// try {
|
|
// let values = [];
|
|
// let paramCounter = 1;
|
|
// let matchingConditionsArray = [];
|
|
|
|
// for (const resourcePath of resourcePathArr) {
|
|
// let depth = getDepth(resourcePath);
|
|
// let condition = `data_depth >= $${paramCounter++}`;
|
|
// values.push(depth); // data_depth 파라미터 추가
|
|
|
|
// for (let i = 0; i < depth; i++) {
|
|
// let num = i + 1;
|
|
// let segment = getPathSegment(resourcePath, num);
|
|
// if (num === 4) {
|
|
// condition += ` AND path${num} IN ($${paramCounter++}, $${paramCounter++}, $${paramCounter++})`;
|
|
// values.push(segment);
|
|
// values.push(`${segment}_version`);
|
|
// values.push(`${segment}_attachment`);
|
|
// } else {
|
|
// condition += ` AND path${num} = $${paramCounter++}`;
|
|
// values.push(segment);
|
|
// }
|
|
// }
|
|
// matchingConditionsArray.push(`(${condition})`);
|
|
// }
|
|
// let matchingConditions = matchingConditionsArray.join(' OR ');
|
|
|
|
// let queryString = `
|
|
// WITH matching_rows AS (
|
|
// SELECT data_id
|
|
// FROM ver4.${tbData}
|
|
// WHERE is_removed = false
|
|
// AND project_id = $${paramCounter++}
|
|
// AND bucket = $${paramCounter++}
|
|
// AND storage_type = $${paramCounter++}
|
|
// AND (${matchingConditions})
|
|
// )
|
|
// UPDATE ver4.${tbData} AS t
|
|
// SET
|
|
// is_removed = true,
|
|
// mod_date = $${paramCounter++},
|
|
// mod_user_id = $${paramCounter++},
|
|
// mod_activity = $${paramCounter++}
|
|
// FROM matching_rows m
|
|
// WHERE t.data_id = m.data_id;
|
|
// `;
|
|
|
|
// values.push(projectId);
|
|
// values.push(bucket);
|
|
// values.push(storageType);
|
|
// values.push(dateNow);
|
|
// values.push(userId);
|
|
// values.push(activity);
|
|
|
|
// // console.log('======================');
|
|
// // console.log(queryString);
|
|
// // console.log(values);
|
|
|
|
// await client.query(queryString, values);
|
|
|
|
// let result = { message: 'updateDataRemove_success' };
|
|
// return result;
|
|
// } catch (error) {
|
|
// console.error(`updateDataRemove error:`, error);
|
|
// } finally {
|
|
// client.release();
|
|
// }
|
|
// }
|
|
|
|
async function updateDataPermission(params) {
|
|
let { projectId, userInfoString, storageType, resourcePath, dataId, activity, dataType, newPermission } = params;
|
|
let bucket = projectId;
|
|
let userInfo = JSON.parse(userInfoString);
|
|
let userId = userInfo.user_id;
|
|
let dateNow = makePostgresTimestamp(Date.now());
|
|
|
|
let depth = getDepth(resourcePath);
|
|
|
|
const client = await pool.connect();
|
|
try {
|
|
let segment1 = getPathSegment(resourcePath, depth);
|
|
let queryString = `
|
|
UPDATE ver4.${tbData}
|
|
SET
|
|
data_permission = ${newPermission},
|
|
mod_date = CASE
|
|
WHEN path${depth} = '${segment1}' AND data_depth = ${depth} THEN '${makePostgresTimestamp(dateNow)}'
|
|
ELSE mod_date
|
|
end,
|
|
mod_user_id = CASE
|
|
WHEN path${depth} = '${segment1}' AND data_depth = ${depth} THEN '${userId}'
|
|
ELSE mod_user_id
|
|
END,
|
|
mod_activity = CASE
|
|
WHEN path${depth} = '${segment1}' AND data_depth = ${depth} THEN '${activity}'
|
|
ELSE mod_activity
|
|
END
|
|
WHERE is_removed = false
|
|
AND project_id = '${projectId}'
|
|
AND bucket = '${bucket}'
|
|
AND storage_type = '${storageType}'`;
|
|
|
|
for (let i = 0; i < depth; i++) {
|
|
let num = i + 1;
|
|
let segment2 = getPathSegment(resourcePath, num);
|
|
if (num === 4) {
|
|
queryString += `
|
|
AND path${num} IN ('${segment2}', '${segment2}_version', '${segment2}_attachment')`;
|
|
} else {
|
|
queryString += `
|
|
AND path${num} = '${segment2}'`;
|
|
}
|
|
}
|
|
|
|
queryString += `;`;
|
|
|
|
await client.query(queryString);
|
|
|
|
let result = { message: 'updateDataPermission_success' };
|
|
return result;
|
|
} catch (error) {
|
|
console.error(`updateDataPermission error:`, error);
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
async function updateDataPosition(params) {
|
|
let { dataId, lon, lat } = params;
|
|
|
|
const client = await pool.connect();
|
|
try {
|
|
let queryString = `
|
|
UPDATE ver4.${tbData}
|
|
SET
|
|
lon = ${lon},
|
|
lat = ${lat}
|
|
WHERE data_id = ${dataId}
|
|
AND is_removed = false;`;
|
|
|
|
await client.query(queryString);
|
|
|
|
let result = { message: 'updateDataPosition_success' };
|
|
return result;
|
|
} catch (error) {
|
|
console.error(`updateDataPosition error:`, error);
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
async function updateLastFolderActDate(arr, inputSeconds) {
|
|
let dataIdString = `(${arr.join(',')})`;
|
|
let dateNow = Date.now();
|
|
let date = makePostgresTimestamp(dateNow);
|
|
if (inputSeconds) date = makePostgresTimestamp(new Date(dateNow - (15 * 24 * 60 * 60 * 1000) + (inputSeconds * 1000)));
|
|
|
|
const client = await pool.connect();
|
|
try {
|
|
let queryString = `
|
|
UPDATE ver4.${tbData}
|
|
SET last_folder_act_date = '${date}'
|
|
WHERE data_id IN ${dataIdString};
|
|
`;
|
|
|
|
// console.log('@@@@@@@@@@@@@@@@@@@@@@');
|
|
// console.log(queryString);
|
|
await client.query(queryString);
|
|
|
|
let result = { message: 'updateLastFolderActDate_success' };
|
|
return result;
|
|
} catch (err) {
|
|
console.error('renewExpiryDate error:', err);
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
async function deleteData(params) {
|
|
let { dataIdArr } = params;
|
|
|
|
const client = await pool.connect();
|
|
try {
|
|
await client.query('BEGIN');
|
|
|
|
let placeholders = dataIdArr.map((_, idx) => `$${idx + 1}`).join(', ');
|
|
|
|
let queryString = `
|
|
DELETE FROM ver4.${tbData}
|
|
WHERE data_id IN (${placeholders})
|
|
`;
|
|
|
|
await client.query(queryString, dataIdArr);
|
|
|
|
await client.query('COMMIT');
|
|
return { message: 'deleteData_success' };
|
|
} catch (err) {
|
|
await client.query('ROLLBACK');
|
|
console.error('deleteData error:', err);
|
|
throw err;
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
//// chat gpt 코드
|
|
async function checkTargetExistsAction(params) {
|
|
let { projectId, storageType, dataType, resourcePathArr } = params;
|
|
let bucket = projectId;
|
|
let isFolder = (dataType === 'folder');
|
|
resourcePathArr = JSON.parse(resourcePathArr);
|
|
|
|
const MAX_DEPTH = 8;
|
|
|
|
const client = await pool.connect();
|
|
try {
|
|
const pathTuples = [];
|
|
for (const path of resourcePathArr) {
|
|
const depth = getDepth(path);
|
|
const segments = [];
|
|
|
|
for (let i = 0; i < depth; i++) {
|
|
segments.push(getPathSegment(path, i + 1));
|
|
}
|
|
|
|
if (depth === 4) {
|
|
['', '_version', '_attachment'].forEach(suffix => {
|
|
const padded = [...segments];
|
|
padded[3] = padded[3] + suffix;
|
|
while (padded.length < MAX_DEPTH) padded.push(null);
|
|
pathTuples.push([depth, ...padded]);
|
|
});
|
|
} else {
|
|
while (segments.length < MAX_DEPTH) segments.push(null);
|
|
pathTuples.push([depth, ...segments]);
|
|
}
|
|
}
|
|
|
|
const colNames = ['data_depth'];
|
|
for (let i = 1; i <= MAX_DEPTH; i++) {
|
|
colNames.push(`path${i}`);
|
|
}
|
|
|
|
const bindValues = [];
|
|
let bindIndex = 1;
|
|
|
|
const valueRows = pathTuples.map(tuple => {
|
|
const rowPlaceholders = tuple.map((_, idx) => {
|
|
const typeCast = (idx === 0) ? '::int' : '::text';
|
|
return `$${bindIndex++}${typeCast}`;
|
|
});
|
|
bindValues.push(...tuple);
|
|
return `(${rowPlaceholders.join(', ')})`;
|
|
});
|
|
|
|
// NULL-safe 비교 조건 생성
|
|
const joinConditions = colNames.map(c => `t.${c} IS NOT DISTINCT FROM v.${c}`).join(' AND ');
|
|
|
|
// console.log('##################################');
|
|
// console.log(valueRows);
|
|
if (valueRows.length === 0) {
|
|
return {
|
|
message: 'checkTargetExistsAction_success',
|
|
rows: []
|
|
};
|
|
}
|
|
|
|
const query = `
|
|
SELECT
|
|
t.data_id, t.project_id, t.user_id, t.create_date, t.data_permission, t.bucket, t.is_folder, t.is_removed, t.data_depth,
|
|
ext, t.path1, t.path2, t.path3, t.path4, t.path5, t.path6, t.path7, t.path8, t.mod_date, t.mod_user_id, t.mod_activity,
|
|
data_size, t.memo, t.storage_type, t.object_key, t.preview_key, t.popup_key, t.ver
|
|
FROM ver4.${tbData} t
|
|
JOIN (
|
|
VALUES ${valueRows.join(',\n')}
|
|
) AS v(${colNames.join(', ')})
|
|
ON ${joinConditions}
|
|
WHERE t.project_id = $${bindIndex++}::text
|
|
AND t.bucket = $${bindIndex++}::text
|
|
AND t.storage_type = $${bindIndex++}::text
|
|
AND t.is_folder = $${bindIndex++}::bool
|
|
AND t.is_removed = false::bool
|
|
`;
|
|
|
|
bindValues.push(projectId, bucket, storageType, isFolder);
|
|
|
|
const { rows } = await client.query(query, bindValues);
|
|
return {
|
|
message: 'checkTargetExistsAction_success',
|
|
rows
|
|
};
|
|
} catch (error) {
|
|
console.error("checkTargetExistsAction err:", error);
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
async function cleanUpExistingData(existingDataIdArr) {
|
|
let existingDataIdString;
|
|
if (existingDataIdArr.length == 1) existingDataIdString = existingDataIdArr[0];
|
|
if (existingDataIdArr.length > 1) existingDataIdString = existingDataIdArr.join(',');
|
|
|
|
const client = await pool.connect();
|
|
try {
|
|
await client.query('BEGIN');
|
|
|
|
// object_key, preview_key, popup_key select 조회 후 row 삭제
|
|
let selectQueryString = `
|
|
SELECT object_key, preview_key, popup_key, thumbnail_key
|
|
FROM ver4.${tbData}
|
|
WHERE data_id in (${existingDataIdString});
|
|
`;
|
|
|
|
let deleteQueryString = `
|
|
DELETE FROM ver4.${tbData}
|
|
WHERE data_id IN (${existingDataIdString});
|
|
`;
|
|
|
|
// console.log('@@@@@@@@@@@@@ selectQueryString');
|
|
// console.log(selectQueryString);
|
|
// console.log('############ deleteQueryString');
|
|
// console.log(deleteQueryString);
|
|
|
|
let { rows } = await client.query(selectQueryString);
|
|
await client.query(deleteQueryString);
|
|
|
|
await client.query('COMMIT');
|
|
|
|
return {
|
|
message: 'cleanUpExistingData_success',
|
|
rows: rows
|
|
}
|
|
} catch(error) {
|
|
console.error("cleanUpExistingData err:", error);
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
async function getFilesCount(projectId, storageType, resourcePath) {
|
|
let depth = getDepth(resourcePath);
|
|
|
|
const client = await pool.connect();
|
|
try {
|
|
//// chat gpt 코드 - path1부터 path8까지 전체비교해서 중복되는 row 있는 경우 한 개만 카운트
|
|
// 1. 내부 서브쿼리 생성 (ROW_NUMBER로 path 중복 제거)
|
|
let innerQuery = `
|
|
SELECT
|
|
ROW_NUMBER() OVER (
|
|
PARTITION BY path1, path2, path3, path4, path5, path6, path7, path8
|
|
ORDER BY mod_date DESC
|
|
) AS row_num
|
|
FROM ver4.${tbData}
|
|
WHERE project_id = '${projectId}'
|
|
AND bucket = '${projectId}'
|
|
AND storage_type = '${storageType}'
|
|
AND is_folder = false
|
|
AND is_removed = false
|
|
AND data_depth >= 4
|
|
`;
|
|
|
|
for (let i = 0; i < depth; i++) {
|
|
let num = i + 1;
|
|
innerQuery += ` AND path${num} = '${getPathSegment(resourcePath, num)}'`;
|
|
}
|
|
|
|
// 2. 바깥쪽 쿼리에서 row_num = 1만 카운트
|
|
let queryString = `
|
|
SELECT count(*)
|
|
FROM (
|
|
${innerQuery}
|
|
) AS sub
|
|
WHERE sub.row_num = 1;
|
|
`;
|
|
|
|
////////////////////////
|
|
|
|
// console.log('==================');
|
|
// console.log(queryString);
|
|
|
|
let { rows } = await client.query(queryString);
|
|
return rows[0].count;
|
|
} catch (error) {
|
|
console.error("getFilesCount err:", error);
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
// async function getObjectKeyTimestamp(params) {
|
|
// const client = await pool.connect();
|
|
// try {
|
|
// let queryString = `
|
|
// SELECT object_key
|
|
// FROM ver4.${tbData}
|
|
// WHERE data_id = '${params.dataId}';
|
|
// `;
|
|
|
|
// let { rows } = await client.query(queryString);
|
|
// let objectKey = rows[0].object_key;
|
|
// let timestamp = objectKey.split('__')[1];
|
|
// return { rows: rows[0], objectKey: objectKey, timestamp: timestamp };
|
|
// } catch(error) {
|
|
// console.error("getFilesCount err:", error)
|
|
// } finally {
|
|
// client.release();
|
|
// }
|
|
// }
|
|
|
|
async function ensureAddOnFolderAction(params) {
|
|
let resourcePath = params.resourcePath;
|
|
let path1 = getPathSegment(resourcePath, 1);
|
|
let path2 = getPathSegment(resourcePath, 2);
|
|
let path3 = getPathSegment(resourcePath, 3);
|
|
let path4 = getPathSegment(resourcePath, 4);
|
|
|
|
const client = await pool.connect();
|
|
try {
|
|
let values = [];
|
|
let paramCounter = 1;
|
|
|
|
let queryString = `
|
|
SELECT
|
|
data_id, project_id, user_id, create_date, data_permission, bucket, is_folder, is_removed, data_depth,
|
|
ext, path1, path2, path3, path4, path5, path6, path7, path8, mod_date, mod_user_id, mod_activity,
|
|
data_size, memo, storage_type, object_key, preview_key, popup_key, ver
|
|
FROM ver4.${tbData}
|
|
WHERE project_id = $${paramCounter++}
|
|
AND bucket = $${paramCounter++}
|
|
AND storage_type = $${paramCounter++}
|
|
AND is_folder = true
|
|
AND is_removed = false
|
|
AND data_depth = $${paramCounter++}
|
|
AND path1 = $${paramCounter++}
|
|
AND path2 = $${paramCounter++}
|
|
AND path3 = $${paramCounter++}
|
|
AND path4 = $${paramCounter++};`;
|
|
|
|
values.push(params.projectId); // $1
|
|
values.push(params.projectId); // $2 (bucket 값)
|
|
values.push(params.storageType); // $3
|
|
values.push(4); // $4 (data_depth 값)
|
|
values.push(path1); // $5
|
|
values.push(path2); // $6
|
|
values.push(path3); // $7
|
|
values.push(path4); // $8
|
|
|
|
// console.log('========================');
|
|
// console.log(queryString);
|
|
// console.log('Values:', values);
|
|
|
|
let { rows } = await client.query(queryString, values);
|
|
|
|
if (rows[0]) {
|
|
return 'check_addOnFolder_success';
|
|
} else {
|
|
let result = await insertData(params);
|
|
return 'create_addOnFolder_success';
|
|
}
|
|
} catch(error) {
|
|
console.error("ensureAddOnFolderAction err:", error)
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
async function getDataInfoAction(params) {
|
|
console.log('');
|
|
console.log('===========================');
|
|
console.log(`async function getDataInfoAction (${makePostgresTimestamp()})`);
|
|
console.log(params.debug);
|
|
console.log('===========================');
|
|
console.log('');
|
|
|
|
let { projectId, storageType, dataIdArr, isRemoved } = params;
|
|
let bucket = projectId;
|
|
|
|
const client = await pool.connect();
|
|
try {
|
|
if (!Array.isArray(dataIdArr) || dataIdArr.length === 0) return [];
|
|
|
|
let dataIdPlaceholders = dataIdArr.map((_, i) => `$${i + 1}`).join(', ');
|
|
let bindStartIndex = dataIdArr.length + 1;
|
|
|
|
let queryString = `
|
|
SELECT
|
|
data_id, project_id, user_id, create_date, data_permission, bucket, is_folder, is_removed, data_depth,
|
|
ext, path1, path2, path3, path4, path5, path6, path7, path8, mod_date, mod_user_id, mod_activity,
|
|
data_size, memo, storage_type, object_key, preview_key, popup_key, thumbnail_key, ver, lon, lat, height, ai_summary
|
|
FROM ver4.${tbData}
|
|
WHERE project_id = $${bindStartIndex}
|
|
AND bucket = $${bindStartIndex + 1}
|
|
AND storage_type = $${bindStartIndex + 2}
|
|
AND is_removed = $${bindStartIndex + 3}
|
|
AND data_id IN (${dataIdPlaceholders});
|
|
`;
|
|
|
|
let values = [...dataIdArr, projectId, bucket, storageType, isRemoved];
|
|
|
|
let { rows } = await client.query(queryString, values);
|
|
return rows;
|
|
} catch(error) {
|
|
console.error("getDataInfoAction err:", error)
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
async function getFolderSizeAction(projectId, storageType) {
|
|
let result = [];
|
|
const client = await pool.connect();
|
|
try {
|
|
let queryString = `
|
|
SELECT
|
|
COALESCE(archive.recycle_bin_size, 0) AS "recycle_bin/size",
|
|
COALESCE(archive.recycle_bin_count, 0) AS "recycle_bin/count",
|
|
COALESCE(archive.data_size, 0) AS "archive/origin/size",
|
|
COALESCE(archive.data_count, 0) AS "archive/origin/count",
|
|
COALESCE(archive.popup_size, 0) AS "archive/pdf/size",
|
|
COALESCE(archive.popup_count, 0) AS "archive/pdf/count",
|
|
COALESCE(archive.preview_size, 0) AS "archive/pdf_thumb/size",
|
|
COALESCE(archive.preview_count, 0) AS "archive/pdf_thumb/count",
|
|
COALESCE(official_doc.data_size, 0) AS "official_doc/origin/size",
|
|
COALESCE(official_doc.data_count, 0) AS "official_doc/origin/count",
|
|
COALESCE(official_doc.popup_size, 0) AS "official_doc/pdf/size",
|
|
COALESCE(official_doc.popup_count, 0) AS "official_doc/pdf/count",
|
|
COALESCE(overview.data_size, 0) AS "overview/size",
|
|
COALESCE(overview.data_count, 0) AS "overview/count"
|
|
FROM
|
|
(
|
|
SELECT
|
|
SUM(CASE WHEN is_removed = true THEN data_size ELSE 0 END) AS recycle_bin_size,
|
|
COUNT(CASE WHEN is_removed = true THEN 1 END) AS recycle_bin_count,
|
|
SUM(CASE WHEN is_removed = false THEN data_size ELSE 0 END) AS data_size,
|
|
COUNT(CASE WHEN is_removed = false THEN 1 END) AS data_count,
|
|
SUM(popup_size) AS popup_size,
|
|
COUNT(CASE WHEN popup_size > 0 THEN 1 END) AS popup_count,
|
|
SUM(preview_size) AS preview_size,
|
|
COUNT(CASE WHEN preview_size > 0 THEN 1 END) AS preview_count
|
|
FROM ver4.${tbData}
|
|
WHERE project_id = '${projectId}'
|
|
AND bucket = '${projectId}'
|
|
AND storage_type = '${storageType}'
|
|
AND is_folder = false
|
|
) AS archive,
|
|
(
|
|
SELECT
|
|
SUM(data_size) AS data_size,
|
|
COUNT(CASE WHEN data_size > 0 THEN 1 END) AS data_count,
|
|
SUM(popup_size) AS popup_size,
|
|
COUNT(CASE WHEN popup_size > 0 THEN 1 END) AS popup_count
|
|
FROM ver4.tb_official_doc_file
|
|
WHERE project_id = '${projectId}'
|
|
AND bucket = '${projectId}'
|
|
AND storage_type = '${storageType}'
|
|
) AS official_doc,
|
|
(
|
|
SELECT
|
|
SUM(value::bigint) AS data_size,
|
|
COUNT(CASE WHEN value::bigint > 0 THEN 1 END) AS data_count
|
|
FROM ver4.tb_overview
|
|
LEFT JOIN LATERAL json_array_elements_text(data_size::json) AS t(value) ON true
|
|
WHERE project_id = '${projectId}' AND data_size IS NOT NULL
|
|
) AS overview
|
|
`;
|
|
|
|
let { rows } = await client.query(queryString);
|
|
let keys = Object.keys(rows[0]);
|
|
let tempArr = [];
|
|
for(let i = 0; i < keys.length; i++) {
|
|
let key = keys[i];
|
|
let splitKey = key.split('/');
|
|
splitKey.pop();
|
|
tempArr.push(splitKey.join('/'));
|
|
}
|
|
|
|
tempArr = new Set(tempArr);
|
|
tempArr = Array.from(tempArr);
|
|
|
|
for(let i = 0; i < tempArr.length; i++) {
|
|
let item = tempArr[i];
|
|
|
|
let obj = {};
|
|
obj['key'] = item;
|
|
obj['size'] = rows[0][`${item}/size`];
|
|
obj['count'] = rows[0][`${item}/count`];
|
|
result.push(obj);
|
|
}
|
|
|
|
return result;
|
|
} catch(error) {
|
|
console.error("getFolderSizeAction err:", error);
|
|
return [];
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
async function selectControlBoxPosition(userId) {
|
|
const client = await pool.connect();
|
|
try {
|
|
let queryString = `
|
|
SELECT user_id, user_nm, company, dept, position, floating_box_position
|
|
FROM ver4.tb_user
|
|
WHERE user_id = $1
|
|
`;
|
|
let values = [userId];
|
|
|
|
let result = await client.query(queryString, values);
|
|
return result.rows;
|
|
} catch(error) {
|
|
console.error("selectControlBoxPosition err:", error);
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
async function updateControlBoxPosition(params) {
|
|
let { userId, positionData } = params;
|
|
|
|
const client = await pool.connect();
|
|
try {
|
|
let queryString = `
|
|
UPDATE ver4.tb_user
|
|
SET floating_box_position = $1
|
|
WHERE user_id = $2
|
|
`;
|
|
|
|
let values = [positionData, userId];
|
|
|
|
await client.query(queryString, values);
|
|
|
|
let result = { message: 'updateControlBoxPosition_success' };
|
|
return result;
|
|
} catch(error) {
|
|
console.error("updateControlBoxPosition err:", error);
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
// 🔺🔺🔺🔺🔺🔺🔺🔺 DB 관련 함수 끝 🔺🔺🔺🔺🔺🔺🔺🔺
|
|
|
|
// 🔻🔻🔻🔻🔻🔻🔻🔻 비즈니스 로직 함수 시작 🔻🔻🔻🔻🔻🔻🔻🔻
|
|
|
|
async function buildTreeObject(projectId, storageType, userInfo, resourcePath) {
|
|
let result = { folder: {}, file: {} };
|
|
resourcePath = resourcePath.startsWith('/') ? resourcePath.slice(1) : resourcePath;
|
|
|
|
try {
|
|
let folder = {}, file = {};
|
|
let dataRows = await selectData(projectId, storageType, userInfo, resourcePath);
|
|
// let removedDataRows = await selectRemovedData(projectId, storageType, userInfo, resourcePath);
|
|
|
|
for (let i = 0; i < dataRows.length; i++) {
|
|
let row = dataRows[i];
|
|
|
|
let dataDepth = row.data_depth;
|
|
|
|
let newResourcePath = '';
|
|
for(let j = 0; j < dataDepth; j++) {
|
|
newResourcePath += '/' + row[`path${j+1}`];
|
|
}
|
|
newResourcePath = newResourcePath.replaceAll('//', '/');
|
|
|
|
let filesCount = 0;
|
|
if (dataDepth <= 3) {
|
|
filesCount = await getFilesCount(projectId, storageType, newResourcePath);
|
|
}
|
|
|
|
if (row.is_folder) {
|
|
let child = (dataDepth >= 2) ? await buildTreeObject(projectId, storageType, userInfo, newResourcePath) : { folder: {}, file: {} };
|
|
|
|
// depth4 추가 폴더dp 파일이 비어있는 경우 해당 row는 treeObject에서 제외
|
|
if (dataDepth == 4 && Object.values(child.folder).length == 0 && Object.values(child.file).length == 0) {
|
|
continue;
|
|
}
|
|
|
|
folder[row[`path${dataDepth}`]] = {
|
|
type: 'folder',
|
|
name: row[`path${dataDepth}`],
|
|
// child: (dataDepth >= 2) ? await buildTreeObject(projectId, storageType, userInfo, newResourcePath) : { folder: {}, file: {} },
|
|
child: child,
|
|
filesCount: filesCount,
|
|
permission: row.data_permission,
|
|
userId: row.user_id,
|
|
userNm: row.user_nm,
|
|
company: row.company,
|
|
dept: row.dept,
|
|
position: row.position,
|
|
createDate: row.create_date,
|
|
size: row.data_size,
|
|
memo: row.memo,
|
|
resourcePath: newResourcePath,
|
|
depth: dataDepth,
|
|
modDate: row.mod_date,
|
|
modUserId: row.mod_user_id,
|
|
modUserNm: row.mod_user_nm,
|
|
modActivity: row.mod_activity,
|
|
storageType: row.storage_type,
|
|
bucket: row.bucket,
|
|
objectKey: row.object_key,
|
|
dataId: Number(row.data_id),
|
|
folderType: row.folder_type,
|
|
lastFolderActDate: row.last_folder_act_date
|
|
}
|
|
} else {
|
|
if (dataDepth == getDepth(newResourcePath)) {
|
|
// 지원여부, 변환필요, 변환완료 변수 생성
|
|
let isSupported = false, needConvert = false, isConverted = false;
|
|
|
|
// 현재 파일 확장자 변수 생성
|
|
let ext = (row.ext).toLowerCase();
|
|
|
|
// 현재 파일 확장자가 지원여부 확장자 배열에 포함되어 있는지 확인해서 isSupported에 저장
|
|
if (supportedExtArr.includes(ext)) isSupported = true;
|
|
// 현재 파일 확장자가 변환필요 확장자 배열에 포함되어 있는지 확인해서 needConvert에 저장
|
|
if (needConvertExtArr.includes(ext)) needConvert = true;
|
|
|
|
// 변환이 필요한 확장자일 때
|
|
if (needConvert) {
|
|
// object_key, preview_key, popup_key에서 파일 이름 추출 후 비교해서
|
|
// object_keyd의 파일이름과 preview_key, popup_key 각각의 파일이름이 동일하면 isConverted true로 설정
|
|
// let objectKeyFileName = getFileNameFromKey(row.object_key);
|
|
// let previewKeyFileName = getFileNameFromKey(row.preview_key);
|
|
// let popupKeyFileName = getFileNameFromKey(row.popup_key);
|
|
// if (objectKeyFileName == previewKeyFileName && objectKeyFileName == popupKeyFileName) {
|
|
if (row.preview_key && row.popup_key) {
|
|
isConverted = true;
|
|
} else {
|
|
isConverted = false;
|
|
}
|
|
}
|
|
|
|
let subCategory, mainFileName;
|
|
if (row.data_depth == 4) mainFileName = row.path4;
|
|
if (row.data_depth == 5) {
|
|
let path4Split = row.path4.split('_');
|
|
subCategory = path4Split.pop();
|
|
mainFileName = path4Split.join('_');
|
|
}
|
|
|
|
file[row[`path${dataDepth}`]] = {
|
|
type: 'file',
|
|
name: row[`path${dataDepth}`],
|
|
isSupported: isSupported,
|
|
needConvert: needConvert,
|
|
isConverted: isConverted,
|
|
permission: row.data_permission,
|
|
userId: row.user_id,
|
|
userNm: row.user_nm,
|
|
company: row.company,
|
|
dept: row.dept,
|
|
position: row.position,
|
|
createDate: row.create_date,
|
|
size: row.data_size,
|
|
memo: row.memo,
|
|
resourcePath: newResourcePath,
|
|
ext: row.ext,
|
|
depth: dataDepth,
|
|
modDate: row.mod_date,
|
|
modUserId: row.mod_user_id,
|
|
modUserNm: row.mod_user_nm,
|
|
modActivity: row.mod_activity,
|
|
storageType: row.storage_type,
|
|
bucket: row.bucket,
|
|
objectKey: row.object_key,
|
|
previewKey: row.preview_key,
|
|
thumbnailKey: row.thumbnail_key,
|
|
dataId: Number(row.data_id),
|
|
lon: row.lon,
|
|
lat: row.lat,
|
|
height: row.height,
|
|
authorId: row.author_id,
|
|
authorNm: row.author_nm,
|
|
mainFileName: mainFileName,
|
|
subCategory: subCategory
|
|
}
|
|
}
|
|
}
|
|
|
|
result = { folder: folder, file: file };
|
|
}
|
|
} catch (err) {
|
|
console.log('---------------- buildTreeObject 실패');
|
|
console.log(err);
|
|
}
|
|
|
|
return await result;
|
|
}
|
|
|
|
async function buildRecycleBinObject(projectId, storageType, userInfo) {
|
|
let result = { recycleBin: {} };
|
|
|
|
try {
|
|
// let recycleBin = {};
|
|
let removedDataRows = await selectRemovedData(projectId, storageType, userInfo);
|
|
|
|
for (let i = 0; i < removedDataRows.length; i++) {
|
|
let row = removedDataRows[i];
|
|
let dataDepth = row.data_depth;
|
|
|
|
let newResourcePath = '';
|
|
for(let j = 0; j < dataDepth; j++) {
|
|
newResourcePath += '/' + row[`path${j+1}`];
|
|
}
|
|
newResourcePath = newResourcePath.replaceAll('//', '/');
|
|
|
|
// 지원여부, 변환필요, 변환완료 변수 생성
|
|
let isSupported = false, needConvert = false, isConverted = false;
|
|
|
|
// 현재 파일 확장자 변수 생성
|
|
let ext = (row.ext).toLowerCase();
|
|
|
|
// 현재 파일 확장자가 지원여부 확장자 배열에 포함되어 있는지 확인해서 isSupported에 저장
|
|
if (supportedExtArr.includes(ext)) isSupported = true;
|
|
// 현재 파일 확장자가 변환필요 확장자 배열에 포함되어 있는지 확인해서 needConvert에 저장
|
|
if (needConvertExtArr.includes(ext)) needConvert = true;
|
|
|
|
// 변환이 필요한 확장자일 때
|
|
if (needConvert) {
|
|
// object_key, preview_key, popup_key에서 파일 이름 추출 후 비교해서
|
|
// object_keyd의 파일이름과 preview_key, popup_key 각각의 파일이름이 동일하면 isConverted true로 설정
|
|
// let objectKeyFileName = getFileNameFromKey(row.object_key);
|
|
// let previewKeyFileName = getFileNameFromKey(row.preview_key);
|
|
// let popupKeyFileName = getFileNameFromKey(row.popup_key);
|
|
// if (objectKeyFileName == previewKeyFileName && objectKeyFileName == popupKeyFileName) {
|
|
if (row.preview_key && row.popup_key) {
|
|
isConverted = true;
|
|
} else {
|
|
isConverted = false;
|
|
}
|
|
}
|
|
|
|
result.recycleBin[`${row[`path${dataDepth}`]}___[recycle-bin]___${i}`] = {
|
|
type: 'file',
|
|
name: row[`path${dataDepth}`],
|
|
isSupported: isSupported,
|
|
needConvert: needConvert,
|
|
isConverted: isConverted,
|
|
permission: row.data_permission,
|
|
userId: row.user_id,
|
|
userNm: row.user_nm,
|
|
company: row.company,
|
|
dept: row.dept,
|
|
position: row.position,
|
|
createDate: row.create_date,
|
|
size: row.data_size,
|
|
memo: row.memo,
|
|
resourcePath: newResourcePath,
|
|
ext: row.ext,
|
|
depth: dataDepth,
|
|
modDate: row.mod_date,
|
|
modUserId: row.mod_user_id,
|
|
modUserNm: row.mod_user_nm,
|
|
modActivity: row.mod_activity,
|
|
storageType: row.storage_type,
|
|
bucket: row.bucket,
|
|
objectKey: row.object_key,
|
|
previewKey: row.preview_key,
|
|
dataId: Number(row.data_id),
|
|
authorId: row.author_id,
|
|
authorNm: row.author_nm,
|
|
}
|
|
}
|
|
// result.recycleBin = recycleBin;
|
|
} catch {
|
|
console.log(111);
|
|
console.log('---------------- buildRecycleBinObject 실패');
|
|
}
|
|
|
|
return await result;
|
|
}
|
|
|
|
async function deleteTargetAction(keyArr, bucket) {
|
|
for (let key of keyArr) {
|
|
const command = new DeleteObjectCommand({
|
|
Bucket: bucket,
|
|
Key: key,
|
|
});
|
|
await s3.send(command);
|
|
// console.log(`⚠ 삭제중 - ${key}`);
|
|
// socket 보내서 클라이언트에서 몇 개중에 몇개 삭제됐는지 프로그레스 표시?
|
|
}
|
|
}
|
|
|
|
// 🔺🔺🔺🔺🔺🔺🔺🔺 비즈니스 로직 함수 끝 🔺🔺🔺🔺🔺🔺🔺🔺
|
|
|
|
|
|
|
|
// 🔻🔻🔻🔻🔻🔻🔻🔻 클라이언트 요청 핸들러 시작 🔻🔻🔻🔻🔻🔻🔻🔻
|
|
|
|
exports.getTreeObject = async (req, res) => {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
|
|
let { params } = req.query;
|
|
let { userInfoString, storageType, resourcePath } = params;
|
|
|
|
let userInfo = JSON.parse(userInfoString);
|
|
|
|
resourcePath = resourcePath.startsWith('/') ? resourcePath.slice(1) : resourcePath;
|
|
// if (resourcePath == '/') resourcePath = '';
|
|
|
|
let currentTreeObject = await buildTreeObject(projectId, storageType, userInfo, resourcePath);
|
|
|
|
console.log('');
|
|
console.log(`---------------- buildTreeObject 성공 - ${userInfo.user_nm} ${userInfo.position} (projectId: ${projectId} / resourcePath: '${resourcePath}')`);
|
|
console.log('');
|
|
|
|
let message = 'getTreeObject_success';
|
|
if (!currentTreeObject) message = 'getTreeObject_failed';
|
|
|
|
res.status(200).json({
|
|
message: message,
|
|
currentTreeObject: currentTreeObject,
|
|
// allTreeObject: allTreeObject,
|
|
convertingDataArr: convertingDataArr,
|
|
});
|
|
}
|
|
|
|
exports.getRecycleBinObject = async (req, res) => {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
|
|
let { params } = req.query;
|
|
let { userInfoString, storageType, resourcePath } = params;
|
|
let userInfo = JSON.parse(userInfoString);
|
|
let recycleBinObject = await buildRecycleBinObject(projectId, storageType, userInfo);
|
|
|
|
console.log('');
|
|
console.log(`---------------- getRecycleBinObject 성공 - ${userInfo.user_nm} ${userInfo.position} (projectId: ${projectId} / resourcePath: '${resourcePath}')`);
|
|
console.log('');
|
|
|
|
let message = 'getRecycleBinObject_success';
|
|
if (!recycleBinObject) message = 'getRecycleBinObject_failed';
|
|
|
|
res.status(200).json({
|
|
message: message,
|
|
recycleBinObject: recycleBinObject
|
|
});
|
|
}
|
|
|
|
exports.getFolderSize = async (req, res) => {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
|
|
let { params } = req.query;
|
|
let { storageType } = params;
|
|
|
|
try {
|
|
let getFolderSizeResult = await getFolderSizeAction(projectId, storageType);
|
|
|
|
res.status(200).json({
|
|
message: 'getFolderSize_success',
|
|
result: getFolderSizeResult || []
|
|
});
|
|
} catch(error) {
|
|
console.error("exports.getFolderSize err:", error);
|
|
res.status(200).json({
|
|
message: 'getFolderSize_failed',
|
|
result: []
|
|
});
|
|
}
|
|
}
|
|
|
|
exports.getLog = async (req, res) => {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
|
|
let selectLogResult = await selectLog(projectId);
|
|
|
|
res.status(200).json({
|
|
message: 'getLog_success',
|
|
logData: selectLogResult,
|
|
});
|
|
|
|
}
|
|
|
|
exports.getUserLog = async (req, res) => {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
|
|
let selectLogResult = await selectUserLog(projectId);
|
|
|
|
res.status(200).json({
|
|
message: 'getUserLog_success',
|
|
logData: selectLogResult,
|
|
});
|
|
|
|
}
|
|
|
|
exports.getFilterLog = async (req, res) => {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
const params = req.query;
|
|
|
|
let selectLogResult = await selectLog(projectId, params);
|
|
|
|
res.status(200).json({
|
|
message: 'getFilterLog_success',
|
|
logData: selectLogResult,
|
|
});
|
|
|
|
}
|
|
|
|
exports.checkTargetExists = async (req, res) => {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
|
|
let { params } = req.body;
|
|
params.projectId = projectId;
|
|
|
|
let checkTargetExistsActionResult = await checkTargetExistsAction(params);
|
|
if (checkTargetExistsActionResult.message == 'checkTargetExistsAction_success') {
|
|
res.status(200).json({
|
|
message: 'checkTargetExists_success',
|
|
rows: checkTargetExistsActionResult.rows
|
|
});
|
|
}
|
|
}
|
|
|
|
exports.createFolder = async (req, res) => {
|
|
try {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
|
|
let { params } = req.body;
|
|
params.projectId = projectId;
|
|
|
|
let activity = 'createFolder';
|
|
let folderType = params.folderType;
|
|
if (folderType) activity = `${activity}-${folderType}`;
|
|
params.activity = activity;
|
|
|
|
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
|
|
});
|
|
}
|
|
}
|
|
|
|
exports.generateUploadUrl = async (req, res, next) => {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
const pageType = req.baseUrl.split('/')[2];
|
|
|
|
let { resourcePath, date, needsThumbnail, thumbnailPath } = req.body;
|
|
let bucket = projectId;
|
|
|
|
// 실시간 권한 조회 검사 (Viewer 권한자 업로드 차단)
|
|
const userGroup = req.user?.group;
|
|
let userPermission = null;
|
|
|
|
if (userGroup === 'super' || userGroup === 'dev' || userGroup === 'USER_GROUP_super') {
|
|
userPermission = 1535;
|
|
} else if (req.user?.user_id) {
|
|
const client = await pool.connect();
|
|
try {
|
|
const queryStr = `
|
|
SELECT
|
|
CASE
|
|
WHEN EXISTS (SELECT 1 FROM ver4.${tbProject} WHERE project_id = $1 AND user_id = $2)
|
|
THEN 255
|
|
ELSE (
|
|
SELECT lev
|
|
FROM ver4.${tbPermission}
|
|
WHERE project_id = $1 AND user_id = $2
|
|
)
|
|
END as lev
|
|
`;
|
|
const checkRes = await client.query(queryStr, [projectId, req.user.user_id]);
|
|
userPermission = checkRes.rows[0]?.lev || null;
|
|
} catch (dbErr) {
|
|
console.error("❌ [Upload Permission Check] DB Error:", dbErr);
|
|
userPermission = null;
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
if (userPermission === null || userPermission <= 1) {
|
|
return res.status(200).json({
|
|
message: 'generateUploadUrl_failed_permission',
|
|
error: '파일 업로드 권한이 없습니다.'
|
|
});
|
|
}
|
|
|
|
// ONPREMISE(MinIO) 환경에서 버킷이 유실되거나 자동 생성되지 않은 경우를 대비한 동적 생성 처리
|
|
if (deploymentType === 'ONPREMISE') {
|
|
try {
|
|
const { CreateBucketCommand, HeadBucketCommand } = require('@aws-sdk/client-s3');
|
|
try {
|
|
await s3.send(new HeadBucketCommand({ Bucket: bucket }));
|
|
} catch (headErr) {
|
|
// NoSuchBucket 등 에러(404) 발생 시 생성 시도
|
|
if (headErr.name === 'NotFound' || headErr.$metadata?.httpStatusCode === 404) {
|
|
console.log(`[generateUploadUrl] 🚀 Bucket '${bucket}' not found on MinIO. Creating...`);
|
|
await s3.send(new CreateBucketCommand({ Bucket: bucket }));
|
|
console.log(`[generateUploadUrl] ✔️ Bucket '${bucket}' created successfully.`);
|
|
} else {
|
|
throw headErr;
|
|
}
|
|
}
|
|
} catch (bucketErr) {
|
|
console.error(`[generateUploadUrl] ⚠️ Failed to verify/create MinIO bucket '${bucket}':`, bucketErr);
|
|
}
|
|
}
|
|
|
|
let originFullPath = getBasePrefix(pageType).origin + resourcePath;
|
|
originFullPath = originFullPath.replaceAll('\\', '/');
|
|
originFullPath = originFullPath.replaceAll('//', '/');
|
|
let objectKey = `${originFullPath}__${makeObjectKeyTimestamp(date)}`;
|
|
let originUrl;
|
|
try {
|
|
const command = new PutObjectCommand({
|
|
Bucket: bucket,
|
|
Key: objectKey,
|
|
ContentType: 'application/octet-stream'
|
|
});
|
|
originUrl = await getSignedUrl(s3, command, { expiresIn: 60 * 60 * 24 });
|
|
} catch (error) {
|
|
console.error('❌ Upload Presigned URL 생성 실패:', error);
|
|
}
|
|
|
|
let thumbnailUrl = null, thumbnailKey = null;
|
|
if (needsThumbnail) {
|
|
let thumbnailFullPath = getBasePrefix(pageType).thumbnail + thumbnailPath;
|
|
thumbnailFullPath = thumbnailFullPath.replaceAll('\\', '/');
|
|
thumbnailFullPath = thumbnailFullPath.replaceAll('//', '/');
|
|
thumbnailKey = `${thumbnailFullPath}__${makeObjectKeyTimestamp(date)}`;
|
|
try {
|
|
const command = new PutObjectCommand({
|
|
Bucket: bucket,
|
|
Key: thumbnailKey,
|
|
ContentType: 'application/octet-stream'
|
|
});
|
|
thumbnailUrl = await getSignedUrl(s3, command, { expiresIn: 60 * 60 * 24 });
|
|
} catch (error) {
|
|
console.error('❌ Upload Presigned URL 생성 실패:', error);
|
|
}
|
|
}
|
|
|
|
res.status(200).json({
|
|
message: 'generateUploadUrl_success',
|
|
result: {
|
|
originUrl: originUrl,
|
|
objectKey: objectKey,
|
|
date: date,
|
|
thumbnailUrl: thumbnailUrl,
|
|
thumbnailKey: thumbnailKey
|
|
}
|
|
});
|
|
}
|
|
|
|
exports.uploadData = async (req, res, next) => {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
|
|
// 실시간 권한 조회 검사 (Viewer 권한자 업로드 차단)
|
|
const userGroup = req.user?.group;
|
|
let userPermission = null;
|
|
|
|
if (userGroup === 'super' || userGroup === 'dev' || userGroup === 'USER_GROUP_super') {
|
|
userPermission = 1535;
|
|
} else if (req.user?.user_id) {
|
|
const client = await pool.connect();
|
|
try {
|
|
const queryStr = `
|
|
SELECT
|
|
CASE
|
|
WHEN EXISTS (SELECT 1 FROM ver4.${tbProject} WHERE project_id = $1 AND user_id = $2)
|
|
THEN 255
|
|
ELSE (
|
|
SELECT lev
|
|
FROM ver4.${tbPermission}
|
|
WHERE project_id = $1 AND user_id = $2
|
|
)
|
|
END as lev
|
|
`;
|
|
const checkRes = await client.query(queryStr, [projectId, req.user.user_id]);
|
|
userPermission = checkRes.rows[0]?.lev || null;
|
|
} catch (dbErr) {
|
|
console.error("❌ [UploadData Permission Check] DB Error:", dbErr);
|
|
userPermission = null;
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
if (userPermission === null || userPermission <= 1) {
|
|
return res.status(200).json({
|
|
message: 'uploadData_failed_permission',
|
|
error: '파일 업로드 권한이 없습니다.'
|
|
});
|
|
}
|
|
|
|
let { params } = req.body;
|
|
params.projectId = projectId;
|
|
params.dateArr = JSON.parse(params.dateArr);
|
|
params.resourcePathArr = JSON.parse(params.resourcePathArr);
|
|
params.sizeArr = JSON.parse(params.sizeArr);
|
|
params.objectKeyArr = JSON.parse(params.objectKeyArr);
|
|
params.thumbnailSizeArr = JSON.parse(params.thumbnailSizeArr);
|
|
params.thumbnailKeyArr = JSON.parse(params.thumbnailKeyArr);
|
|
params.existingDataIdArr = JSON.parse(params.existingDataIdArr);
|
|
params.coordArr = JSON.parse(params.coordArr);
|
|
|
|
let activity = `uploadData_${params.dataType}`;
|
|
if (params.functionId.includes('addOn_')) activity = params.functionId;
|
|
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 updateLastFolderActDateResult = await updateLastFolderActDate(params.depth3DataIdArr);
|
|
// if (updateLastFolderActDateResult.message == 'updateLastFolderActDate_success') {
|
|
// }
|
|
|
|
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,
|
|
activity: activity,
|
|
resourcePathArr: params.resourcePathArr
|
|
};
|
|
|
|
if (params.existingDataIdArr.length != 0) {
|
|
// params.existingDataIdArr에 들어있는 중복 파일 id를 사용해서
|
|
// cleanUpExistingData에서 중복 파일 row를 조회한 뒤 row 삭제하고, 조회한 row 결과 반환
|
|
let cleanUpExistingDataResult = await cleanUpExistingData(params.existingDataIdArr);
|
|
if (cleanUpExistingDataResult.message == 'cleanUpExistingData_success') {
|
|
// 반환한 row에 담긴 object_key, preview_key, popup_key를 keyArr 배열에 담고 중복키 제거한 뒤
|
|
// keyArr 배열 사용해서 오브젝트 스토리지에서 파일 삭제
|
|
let keyArr = [];
|
|
for (let row of cleanUpExistingDataResult.rows) {
|
|
if (row.object_key) keyArr.push(row.object_key);
|
|
if (row.preview_key) keyArr.push(row.preview_key);
|
|
if (row.popup_key) keyArr.push(row.popup_key);
|
|
if (row.thumbnail_key) keyArr.push(row.thumbnail_key);
|
|
}
|
|
keyArr = [...new Set(keyArr)];
|
|
|
|
await deleteTargetAction(keyArr, projectId);
|
|
}
|
|
}
|
|
|
|
let io = getIo();
|
|
io.emit('uploadData_success', resultData);
|
|
|
|
res.status(200).json({
|
|
message: 'uploadData_success',
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
exports.ensureAddOnFolder = async (req, res, next) => {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
|
|
let { params } = req.body;
|
|
params.projectId = projectId;
|
|
params.dateArr = [params.date];
|
|
params.resourcePathArr = [params.resourcePath];
|
|
params.dataType = 'folder';
|
|
|
|
let result = await ensureAddOnFolderAction(params);
|
|
if (result == 'check_addOnFolder_success' || result == 'create_addOnFolder_success') {
|
|
res.status(200).json({
|
|
message: 'ensureAddOnFolder_success',
|
|
});
|
|
}
|
|
}
|
|
|
|
exports.renameTarget = async(req, res) => {
|
|
let { params } = req.body;
|
|
let permission = JSON.parse(params.userInfoString).permission;
|
|
let depth = getDepth(params.resourcePath);
|
|
|
|
if ((depth == 1 && permission < 191) || (depth >= 2 && permission < 7)) {
|
|
res.status(200).json({
|
|
message: 'renameTarget_failed_permission',
|
|
});
|
|
} else {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
params.projectId = projectId;
|
|
|
|
let activity = `renameTarget_${params.dataType}`;
|
|
params.activity = activity;
|
|
|
|
let updateDataRenameResult = await updateDataRename(params);
|
|
if (updateDataRenameResult.message == 'updateDataRename_success') {
|
|
params.userIp = req.ip;
|
|
params.resourcePathArr = [params.oldPath, params.newPath];
|
|
params.dataIdArr = [params.dataId];
|
|
|
|
let insertLogResult = await insertLog(params);
|
|
if (insertLogResult.message == 'insertLog_success') {
|
|
let resultData = {
|
|
message: `renameTarget_success`,
|
|
projectId: projectId,
|
|
activity: activity,
|
|
oldPath: params.oldPath,
|
|
newPath: params.newPath
|
|
};
|
|
|
|
let io = getIo();
|
|
io.emit('renameTarget_success', resultData);
|
|
|
|
res.status(200).json({
|
|
message: 'renameTarget_success',
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
exports.editAuthor = async (req, res) => {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
|
|
let { params } = req.body;
|
|
params.projectId = projectId;
|
|
params.activity = 'editAuthor';
|
|
|
|
let updateDataAuthorResult = await updateDataAuthor(params);
|
|
if (updateDataAuthorResult.message == 'updateDataAuthor_success') {
|
|
let originalResourcePathArr = params.resourcePathArr;
|
|
let newResourcePathArr = [];
|
|
for (let i = 0; i < originalResourcePathArr.length; i++) {
|
|
let resourcePath = originalResourcePathArr[i];
|
|
let prevAuthorId = params.prevAuthorIdArr[i];
|
|
let prevAuthorNm = params.prevAuthorNmArr[i];
|
|
|
|
let obj = {
|
|
resourcePath: resourcePath,
|
|
prevAuthorId: prevAuthorId,
|
|
prevAuthorNm: prevAuthorNm,
|
|
newAuthorId: params.newAuthorId,
|
|
newAuthorNm: params.newAuthorNm
|
|
};
|
|
newResourcePathArr.push(obj);
|
|
}
|
|
params.resourcePathArr = newResourcePathArr;
|
|
params.userIp = req.ip;
|
|
|
|
let insertLogResult = await insertLog(params);
|
|
if (insertLogResult.message == 'insertLog_success') {
|
|
params.message = 'editAuthor_success';
|
|
params.originalResourcePathArr = originalResourcePathArr;
|
|
let resultData = params;
|
|
|
|
let io = getIo();
|
|
io.emit('editAuthor_success', resultData);
|
|
|
|
res.status(200).json({
|
|
message: 'editAuthor_success',
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
exports.getDataInfo = async (req, res) => {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
|
|
let { params } = req.body;
|
|
params.projectId = projectId;
|
|
if (typeof params.dataIdArr === 'string') params.dataIdArr = JSON.parse(params.dataIdArr);
|
|
|
|
console.log('');
|
|
console.log('@@@@@@@@@@@@@@@@@@@@@@@@@@@');
|
|
console.log(`exports.getDataInfo (${makePostgresTimestamp()})`);
|
|
console.log(params.debug);
|
|
console.log('@@@@@@@@@@@@@@@@@@@@@@@@@@@');
|
|
console.log('');
|
|
|
|
try {
|
|
let result = await getDataInfoAction(params);
|
|
if (result && result.length == 1) result = result[0];
|
|
|
|
res.status(200).json({
|
|
message: 'getDataInfo_success',
|
|
result: result || null,
|
|
});
|
|
} catch (err) {
|
|
console.error("exports.getDataInfo err:", err);
|
|
res.status(500).json({
|
|
message: 'getDataInfo_error',
|
|
error: err.message
|
|
});
|
|
}
|
|
}
|
|
|
|
exports.generateDownloadUrl = async (req, res, next) => {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
|
|
let { objectKey, resourcePath, isThumbnail } = req.body;
|
|
let bucket = projectId;
|
|
let fileName = resourcePath.split('/').pop();
|
|
let ext = (objectKey.split('.').pop().toLowerCase()).split('__')[0];
|
|
|
|
try {
|
|
let isInlineType = ['jpg', 'jpeg', 'png', 'webp', 'gif']; // 웹 이미지 확장자
|
|
let responseContentDispositionString, responseContentTypeString;
|
|
if (isInlineType.includes(ext)) {
|
|
responseContentDispositionString = `attachment; filename="${encodeURIComponent(fileName)}"`;
|
|
responseContentTypeString = getMimeType(ext);
|
|
} else {
|
|
responseContentDispositionString = `attachment; filename="${encodeURIComponent(fileName)}"`;
|
|
responseContentTypeString = 'application/octet-stream';
|
|
}
|
|
|
|
let command = new GetObjectCommand({
|
|
Bucket: bucket,
|
|
Key: objectKey,
|
|
// 아래 두 옵션을 넣으면 브라우저가 파일을 바로 열지 않고 다운로드로 처리하도록 서버 응답 헤더를 강제설정할 수 있음:
|
|
ResponseContentDisposition: responseContentDispositionString,
|
|
ResponseContentType: responseContentTypeString,
|
|
ResponseCacheControl: 'no-cache',
|
|
});
|
|
|
|
let time = 60 * 60 * 24; // 유효시간: 60분(1시간) * 24 = 24시간 하루
|
|
if (isThumbnail) time = 60 * 60 * 24; // 유효시간: 60분(1시간) * 24 = 24시간 하루
|
|
let url = await getSignedUrl(s3, command, { expiresIn: time });
|
|
res.status(200).json({
|
|
message: 'generateDownloadUrl_success',
|
|
url: url
|
|
});
|
|
} catch (error) {
|
|
console.error('❌ Download Presigned URL 생성 실패:', error);
|
|
}
|
|
|
|
function getMimeType(ext) {
|
|
const mimeMap = {
|
|
gif: 'image/gif',
|
|
jpg: 'image/jpeg',
|
|
jpeg: 'image/jpeg',
|
|
png: 'image/png',
|
|
webp: 'image/webp'
|
|
};
|
|
return mimeMap[ext] || 'application/octet-stream';
|
|
}
|
|
}
|
|
|
|
exports.downloadTarget = async(req, res) => {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
|
|
let { params } = req.body;
|
|
params.projectId = projectId;
|
|
|
|
let activity = `downloadTarget_${params.dataType}`;
|
|
params.activity = activity;
|
|
|
|
params.userIp = req.ip;
|
|
|
|
let insertLogResult = await insertLog(params);
|
|
if (insertLogResult.message == 'insertLog_success') {
|
|
let io = getIo();
|
|
io.emit('downloadTarget_success', params);
|
|
|
|
res.status(200).json({
|
|
message: 'downloadTarget_success',
|
|
});
|
|
}
|
|
}
|
|
|
|
exports.relocateTarget = async(req, res) => {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
|
|
let { params } = req.body;
|
|
params.projectId = projectId;
|
|
|
|
let activity = `relocateTarget_${params.dataType}`;
|
|
params.activity = activity;
|
|
|
|
let updateDataRelocateResult = await updateDataRelocate(params);
|
|
if (updateDataRelocateResult.message == 'updateDataRelocate_success') {
|
|
params.userIp = req.ip;
|
|
let fromPathArr = params.fromPathArr;
|
|
let toPathArr = params.toPathArr;
|
|
let resourcePathArr = [];
|
|
for (let i = 0; i < fromPathArr.length; i++) {
|
|
let obj = {};
|
|
obj.from = fromPathArr[i];
|
|
obj.to = toPathArr[i];
|
|
resourcePathArr.push(obj);
|
|
}
|
|
|
|
params.resourcePathArr = resourcePathArr;
|
|
|
|
let updateLastFolderActDateResult = await updateLastFolderActDate(params.depth3DataIdArr);
|
|
// if (updateLastFolderActDateResult.message == 'updateLastFolderActDate_success') {
|
|
// }
|
|
|
|
let insertLogResult = await insertLog(params);
|
|
if (insertLogResult.message == 'insertLog_success') {
|
|
|
|
let resultData = {
|
|
message: `relocateTarget_success`,
|
|
projectId: projectId,
|
|
activity: activity,
|
|
resourcePathArr: params.resourcePathArr,
|
|
userInfoString: params.userInfoString
|
|
};
|
|
|
|
let io = getIo();
|
|
io.emit('relocateTarget_success', resultData);
|
|
|
|
res.status(200).json({
|
|
message: 'relocateTarget_success',
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
exports.removeTarget = async(req, res) => {
|
|
try {
|
|
let { params } = req.body;
|
|
let permission = JSON.parse(params.userInfoString).permission;
|
|
let depth = getDepth(params.resourcePathArr[0]);
|
|
let isRecycleBinModal = params.isRecycleBinModal;
|
|
const isExpiredFolder = params.isExpiredFolder === true;
|
|
|
|
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',
|
|
});
|
|
} else {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
params.projectId = projectId;
|
|
|
|
let activity = `removeTarget_${params.dataType}`;
|
|
params.activity = activity;
|
|
|
|
let updateDataRemoveResult = await updateDataRemove(params);
|
|
if (updateDataRemoveResult && updateDataRemoveResult.message == 'updateDataRemove_success') {
|
|
params.userIp = req.ip;
|
|
|
|
if (params.dataType == 'file') {
|
|
let updateLastFolderActDateResult = await updateLastFolderActDate(params.depth3DataIdArr);
|
|
}
|
|
|
|
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
|
|
});
|
|
}
|
|
}
|
|
|
|
exports.deleteTarget = async(req, res) => {
|
|
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: 'deleteTarget_failed_permission',
|
|
});
|
|
} else {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
params.projectId = projectId;
|
|
params.resourcePathArr = JSON.parse(params.resourcePathArr);
|
|
params.dataIdArr = JSON.parse(params.dataIdArr);
|
|
params.objectKeyArr = JSON.parse(params.objectKeyArr);
|
|
params.previewKeyArr = JSON.parse(params.previewKeyArr);
|
|
params.popupKeyArr = JSON.parse(params.popupKeyArr);
|
|
params.thumbnailKeyArr = JSON.parse(params.thumbnailKeyArr);
|
|
|
|
let activity = `deleteTarget_${params.dataType}`;
|
|
params.activity = activity;
|
|
|
|
// 모든 키를 담는 keyArr 배열 생성
|
|
let keyArr = [...params.objectKeyArr, ...params.previewKeyArr, ...params.popupKeyArr, ...params.thumbnailKeyArr];
|
|
|
|
// 중복된 키 제거
|
|
keyArr = [...new Set(keyArr)];
|
|
|
|
// null, '', undefined 등 불필요한 값 제거
|
|
keyArr = keyArr.filter(key =>
|
|
typeof key === 'string' ? key.trim() !== '' : Boolean(key)
|
|
);
|
|
|
|
let deleteDataResult = await deleteData(params);
|
|
if (deleteDataResult.message == 'deleteData_success') {
|
|
// 완전 삭제는 DB삭제 후 오브젝트 스토리지에서 삭제하는데,
|
|
// DB에서 삭제되면 더 이상 조회되지 않아 파일 관련 기능을 사용할 수 없으므로
|
|
// 오브젝트 스토리지에서 삭제하기 전에 소켓으로 미리 갱신
|
|
let successResultData = {
|
|
message: `deleteTarget_success`,
|
|
projectId: projectId,
|
|
activity: activity,
|
|
userInfoString: params.userInfoString
|
|
};
|
|
let io = getIo();
|
|
io.emit('deleteTarget_success', successResultData);
|
|
|
|
try {
|
|
await deleteTargetAction(keyArr, projectId);
|
|
|
|
console.log('❗❗ 오브젝트 스토리지 삭제 완료');
|
|
// 로그 추가
|
|
|
|
res.status(200).json({
|
|
message: 'deleteTarget_success',
|
|
});
|
|
} catch (err) {
|
|
console.error('deleteTarget error:', err);
|
|
throw err;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
exports.setDataPermission = async(req, res) => {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
|
|
let { params } = req.body;
|
|
params.projectId = projectId;
|
|
|
|
let activity = `setDataPermission_${params.dataType}`;
|
|
params.activity = activity;
|
|
|
|
let updateDataPermissionResult = await updateDataPermission(params);
|
|
if (updateDataPermissionResult.message == 'updateDataPermission_success') {
|
|
params.resourcePathArr = [{
|
|
resourcePath: params.resourcePath,
|
|
beforePermission: getPermissionString(params.beforePermission),
|
|
newPermission: getPermissionString(params.newPermission),
|
|
}];
|
|
params.dataIdArr = [params.dataId];
|
|
params.userIp = req.ip;
|
|
|
|
let insertLogResult = await insertLog(params);
|
|
if (insertLogResult.message == 'insertLog_success') {
|
|
let resultData = {
|
|
message: `setDataPermission_success`,
|
|
projectId: projectId,
|
|
activity: activity,
|
|
resourcePath: params.resourcePath,
|
|
userInfoString: params.userInfoString,
|
|
beforePermission: params.beforePermission,
|
|
newPermission: params.newPermission,
|
|
};
|
|
|
|
let io = getIo();
|
|
io.emit('setDataPermission_success', resultData);
|
|
|
|
res.status(200).json({
|
|
message: 'setDataPermission_success',
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
exports.editPosition = async(req, res) => {
|
|
let { params } = req.body;
|
|
let updateDataPositionResult = await updateDataPosition(params);
|
|
if (updateDataPositionResult.message == 'updateDataPosition_success') {
|
|
res.status(200).json({
|
|
message: 'editPosition_success',
|
|
});
|
|
}
|
|
}
|
|
|
|
exports.renewExpiryDate = async(req, res) => {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
|
|
let { params } = req.body;
|
|
let inputSeconds = (params.inputSeconds) ? params.inputSeconds : undefined;
|
|
|
|
let updateLastFolderActDateResult = await updateLastFolderActDate(params.depth3DataIdArr, inputSeconds);
|
|
if (updateLastFolderActDateResult.message == 'updateLastFolderActDate_success') {
|
|
let resultData = {
|
|
message: `renewExpiryDate_success`,
|
|
projectId: projectId,
|
|
resourcePath: params.resourcePath,
|
|
dataId: params.dataId,
|
|
userInfoString: params.userInfoString,
|
|
inputSeconds: inputSeconds
|
|
}
|
|
|
|
let io = getIo();
|
|
io.emit('renewExpiryDate_success', resultData);
|
|
|
|
res.status(200).json({
|
|
message: 'renewExpiryDate_success',
|
|
});
|
|
}
|
|
}
|
|
|
|
//pdf_thumb
|
|
exports.makeThumbPdf = async(req, res)=>{
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
let bucket = projectId;
|
|
let userIp = req.ip;
|
|
let { resourcePath, userInfoString, objectKey, storageType } = req.body.params;
|
|
|
|
let dataId = undefined;
|
|
|
|
let command = new GetObjectCommand({
|
|
Bucket: bucket,
|
|
Key: objectKey,
|
|
});
|
|
|
|
const client = await pool.connect();
|
|
try{
|
|
|
|
let queryString = `select data_id from ver4.${tbData} where project_id = $1 and object_key = $2`;
|
|
let Res = await client.query(queryString, [projectId, objectKey]);
|
|
|
|
|
|
dataId = Res.rows[0].data_id;
|
|
let url = await getSignedUrl(s3, command, { expiresIn: 60 * 60 * 15 }); // 유효시간: 15분
|
|
|
|
let waitingCount = await thumbQueue.getWaitingCount();
|
|
|
|
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 thumbQueue.add(
|
|
`'${initiator}'에서 PDF 업로드(Thumb생성)`,
|
|
{ resourcePath, url, objectKey, bucket, storageType, dataId, projectId, userInfoString, userIp, initiator, type },
|
|
);
|
|
|
|
res.status(200).json({
|
|
jobId: job.id,
|
|
waiting: waitingCount
|
|
});
|
|
}catch(err){
|
|
console.error(err);
|
|
res.status(500).json({
|
|
message : 'makeThumb Error',
|
|
});
|
|
}finally{
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
exports.convertPdf = async(req, res) => {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
let bucket = projectId;
|
|
let userIp = req.ip;
|
|
|
|
let { params } = req.body;
|
|
let { dataId, resourcePath, depth1, depth2, depth3, userInfoString, objectKey, storageType } = params;
|
|
|
|
let command = new GetObjectCommand({
|
|
Bucket: bucket,
|
|
Key: objectKey,
|
|
});
|
|
|
|
let url = await getSignedUrl(s3, command, { expiresIn: 60 * 60 * 6 }); // 유효시간: 6시간
|
|
|
|
let waitingCount = await convertPdfQueue.getWaitingCount();
|
|
|
|
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 },
|
|
// { jobId: `pdf-${Date.now()}` } // ← 원하는 ID 값 지정
|
|
);
|
|
|
|
res.status(200).json({
|
|
jobId: job.id,
|
|
waiting: waitingCount
|
|
});
|
|
|
|
//// 배열에 현재 변환중인 파일 정보 추가
|
|
convertingDataArr.push({
|
|
dataId: dataId,
|
|
resourcePath: resourcePath,
|
|
depth1: depth1,
|
|
depth2: depth2,
|
|
depth3: depth3,
|
|
jobId: job.id
|
|
})
|
|
|
|
//// 변환 시작 socket 전송
|
|
let resultData = { projectId: projectId, resourcePath: resourcePath, convertingDataArr: convertingDataArr };
|
|
let io = getIo();
|
|
io.emit('convert_start', resultData);
|
|
|
|
// queue.js에서 변환 완료/실패 후 소켓으로 convertPdf_success/convertPdf_failed 이벤트 전송
|
|
// console.log('@@@@@@@@@@@@@@');
|
|
// console.log(convertingDataArr);
|
|
}
|
|
|
|
exports.addConvetPdfLog = async(req, res) => {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
|
|
let { params } = req.body;
|
|
let { userInfoString, resourcePath, dataId } = params
|
|
|
|
//// 배열에서 파일 정보 삭제
|
|
convertingDataArr = convertingDataArr.filter(data => data.dataId !== dataId);
|
|
|
|
const waiting = await convertPdfQueue.getWaitingCount();
|
|
const active = await convertPdfQueue.getActiveCount();
|
|
if (waiting == 0 && active == 0 && convertingDataArr.length != 0) {
|
|
convertingDataArr = [];
|
|
}
|
|
|
|
let insertLogResult = await insertLog(params, 'addConvertPdfLog');
|
|
if (insertLogResult.message == 'insertLog_success') {
|
|
let resultData = {
|
|
message: 'addConvetPdfLog_success',
|
|
projectId: projectId,
|
|
userInfoString: userInfoString,
|
|
resourcePath: resourcePath,
|
|
dataId: dataId,
|
|
convertingDataArr: convertingDataArr
|
|
};
|
|
|
|
let io = getIo();
|
|
io.emit('addConvetPdfLog_success', resultData);
|
|
|
|
res.status(200).json({
|
|
message: 'addConvetPdfLog_success',
|
|
});
|
|
}
|
|
}
|
|
|
|
exports.removeConvertingData = async(req, res) => {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
let { params } = req.body;
|
|
let { resourcePath, dataId, userInfoString, stdout } = params;
|
|
|
|
//// 배열에서 파일 정보 삭제
|
|
convertingDataArr = convertingDataArr.filter(data => data.dataId !== dataId);
|
|
|
|
const waiting = await convertPdfQueue.getWaitingCount();
|
|
const active = await convertPdfQueue.getActiveCount();
|
|
if (waiting == 0 && active == 0 && convertingDataArr.length != 0) {
|
|
convertingDataArr = [];
|
|
}
|
|
|
|
let resultData = {
|
|
message: 'removeConvertingData_success',
|
|
projectId: projectId,
|
|
convertingDataArr: convertingDataArr,
|
|
resourcePath: resourcePath,
|
|
dataId: dataId,
|
|
userInfoString: userInfoString,
|
|
stdout: stdout || ''
|
|
};
|
|
|
|
let io = getIo();
|
|
io.emit('removeConvertingData_success', resultData);
|
|
|
|
res.status(200).json({
|
|
message: 'removeConvertingData_success',
|
|
resultData: resultData
|
|
});
|
|
}
|
|
|
|
exports.postProcessVideo = async(req, res) => {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
let { resourcePath, userInfoString, objectKey, storageType } = req.body.params;
|
|
let bucket = projectId;
|
|
let userIp = req.ip;
|
|
let dataId = undefined;
|
|
|
|
const client = await pool.connect();
|
|
try {
|
|
let queryString = `select data_id from ver4.${tbData} where project_id = $1 and object_key = $2`;
|
|
let result = await client.query(queryString, [projectId, objectKey]);
|
|
|
|
dataId = result.rows[0].data_id;
|
|
|
|
let waitingCount = await postProcessVideoQueue.getWaitingCount();
|
|
|
|
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 postProcessVideoQueue.add(
|
|
`'${initiator}'에서 동영상 후처리`,
|
|
{ resourcePath, objectKey, bucket, storageType, dataId, projectId, userInfoString, userIp, initiator, type },
|
|
);
|
|
|
|
res.status(200).json({
|
|
jobId: job.id,
|
|
waiting: waitingCount
|
|
});
|
|
} catch (err) {
|
|
console.error(err);
|
|
res.status(500).json({
|
|
message : 'postProcessVideo Error',
|
|
});
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
exports.requestResetViewer = async(req, res) => {
|
|
|
|
}
|
|
|
|
exports.updateMemoInfo = async(req, res) => {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
const { userInfoString, params} = req.body;
|
|
|
|
let userInfo = JSON.parse(userInfoString);
|
|
let userId = (userInfo)? userInfo.user_id : undefined;
|
|
if(!userId) return;
|
|
|
|
let parts = params.resourcePath.split('/').filter(Boolean);
|
|
|
|
const client = await pool.connect();
|
|
try {
|
|
let string = [];
|
|
for(let i=0; i<parts.length; i++) {
|
|
let escapeParts = parts[i].replaceAll("'", "''");
|
|
string.push(` and path${i+1} = '${escapeParts}'`);
|
|
}
|
|
let escapeMemo = params.memo.replaceAll("'", "''");
|
|
let queryString = `
|
|
update ver4.${tbData}
|
|
set memo = '${escapeMemo}'
|
|
where project_id = '${projectId}'
|
|
`;
|
|
string.forEach(str => {
|
|
queryString += str;
|
|
})
|
|
|
|
let result = client.query(queryString);
|
|
let resultData = {
|
|
projectId: projectId,
|
|
resourcePath: params.resourcePath,
|
|
memo: params.memo,
|
|
}
|
|
|
|
let io = getIo();
|
|
io.emit('saveMemo_success', resultData);
|
|
|
|
res.status(200).json({
|
|
message: 'updateMemo_success'
|
|
});
|
|
|
|
}catch(error) {
|
|
console.error('selectData err:', error);
|
|
}finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
exports.getMemoInfo = async (req, res) => {
|
|
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
const { userInfoString, resourcePath, dataId } = req.query;
|
|
|
|
let userInfo = JSON.parse(userInfoString);
|
|
let userId = (userInfo)? userInfo.user_id : undefined;
|
|
if(!userId) return;
|
|
|
|
let parts = resourcePath.split('/').filter(Boolean);
|
|
|
|
const client = await pool.connect();
|
|
try {
|
|
// let string;
|
|
// for(let i=0; i<parts.length; i++) {
|
|
// parts[i] = parts[i].replace(/'/g, "\\'");
|
|
// string = `and path${i+1} = '${parts[i]}'`;
|
|
// }
|
|
|
|
// let queryString = `
|
|
// select memo
|
|
// from ver4.${tbData}
|
|
// where project_id = '${projectId}'
|
|
// ${string}
|
|
// `;
|
|
|
|
// let result = await client.query(queryString);
|
|
// res.status(200).json({
|
|
// message: 'selectMemo_success',
|
|
// result: result.rows[0]
|
|
// });
|
|
|
|
const queryParams = [projectId];
|
|
queryParams.push(dataId);
|
|
|
|
let paramIndex = 3;
|
|
|
|
let conditionParts = [];
|
|
for (let i = 0; i < parts.length; i++) {
|
|
conditionParts.push(`path${i+1} = $${paramIndex}`);
|
|
queryParams.push(parts[i]);
|
|
paramIndex++;
|
|
}
|
|
|
|
let queryString = `
|
|
SELECT memo
|
|
FROM ver4.${tbData}
|
|
WHERE project_id = $1
|
|
AND data_id = $2
|
|
${conditionParts.length > 0 ? 'AND ' + conditionParts.join(' AND ') : ''}
|
|
`;
|
|
|
|
let result = await client.query(queryString, queryParams);
|
|
res.status(200).json({
|
|
message: 'selectMemo_success',
|
|
result: result.rows[0]
|
|
});
|
|
|
|
}catch(error) {
|
|
console.error('selectData err:', error);
|
|
}finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
// main title img 관련
|
|
exports.uploadData_titleImg = async(req, res, next) => {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
|
|
let { params } = req.body;
|
|
params.projectId = projectId;
|
|
params.dateArr = JSON.parse(params.dateArr);
|
|
params.resourcePathArr = JSON.parse(params.resourcePathArr);
|
|
params.sizeArr = JSON.parse(params.sizeArr);
|
|
params.objectKeyArr = JSON.parse(params.objectKeyArr);
|
|
|
|
let activity = `uploadData_${params.dataType}`;
|
|
|
|
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: 'uploadData_success',
|
|
projectId: projectId,
|
|
activity: activity,
|
|
resourcePathArr: params.resourcePathArr
|
|
};
|
|
|
|
let io = getIo();
|
|
io.emit('uploadData_success', resultData);
|
|
|
|
res.status(200).json({
|
|
message: 'uploadData_success',
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
exports.generateImageUrl = async (req, res, next) => {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
const pageType = req.baseUrl.split('/')[2];
|
|
|
|
let { resourcePath } = req.query;
|
|
let bucket = projectId;
|
|
|
|
let fullPath = resourcePath;
|
|
fullPath = fullPath.replaceAll('\\', '/');
|
|
fullPath = fullPath.replaceAll('//', '/');
|
|
fullPath = fullPath.split('/')[1];
|
|
|
|
const client = await pool.connect();
|
|
try {
|
|
let queryString = `select object_key from ver4.${tbData}
|
|
where project_id = '${projectId}'
|
|
and path1 = $1
|
|
and data_depth = 2
|
|
and is_folder = false
|
|
and is_removed = false`
|
|
|
|
let {rows} = await client.query(queryString, [fullPath]);
|
|
|
|
let objectKey = rows[0].object_key
|
|
|
|
try {
|
|
const command = new GetObjectCommand({
|
|
Bucket: bucket,
|
|
Key: objectKey,
|
|
ContentType: 'application/octet-stream'
|
|
});
|
|
url = await getSignedUrl(s3, command, { expiresIn: 60 * 60 * 15 }); // 유효시간: 15분
|
|
}catch(error) {
|
|
console.error('❌ Image Presigned URL 생성 실패:', error)
|
|
}
|
|
|
|
res.status(200).json({
|
|
message: 'generateImageUrl_success',
|
|
result: {
|
|
url: url
|
|
}
|
|
});
|
|
|
|
}catch(error) {
|
|
console.error('generateImageUrl err:', error);
|
|
}finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
exports.isMainTitleImage = async(req, res) => {
|
|
const client = await pool.connect();
|
|
try {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
let { resourcePath } = req.query;
|
|
resourcePath = resourcePath.replace('/', '');
|
|
|
|
let queryString = `
|
|
select path2
|
|
from ver4.${tbData}
|
|
where project_id = '${projectId}'
|
|
and path1 = $1
|
|
and data_depth = 2
|
|
and is_folder = false
|
|
and is_removed = false;
|
|
`
|
|
let result = await client.query(queryString, [resourcePath]);
|
|
res.status(200).json({
|
|
message: 'isMainTitleImage_true',
|
|
result: result.rows[0]
|
|
})
|
|
|
|
}catch(error) {
|
|
console.error('isMainTitleImage err:', error);
|
|
}finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
exports.deleteMainTitleImage = async(req, res) => {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
|
|
let { params } = req.body;
|
|
let { userInfoString, resourcePath } = params;
|
|
|
|
let userInfo = JSON.parse(userInfoString);
|
|
let userId = (userInfo)? userInfo.user_id : '-';
|
|
let dateNow = makePostgresTimestamp(Date.now());
|
|
resourcePath = resourcePath.split('/')[1];
|
|
|
|
const client = await pool.connect();
|
|
try {
|
|
|
|
// DB 삭제
|
|
let queryString = `
|
|
update ver4.${tbData} set
|
|
is_removed = true,
|
|
mod_date = $1,
|
|
mod_user_id = $2,
|
|
mod_activity = $3
|
|
where project_id = '${projectId}'
|
|
and path1 = $4
|
|
and is_folder = false
|
|
and data_depth = 2`
|
|
;
|
|
|
|
let values = [dateNow, userId, 'removeTarget_file', resourcePath];
|
|
|
|
let result = await client.query(queryString, values);
|
|
|
|
res.status(200).json({
|
|
message: 'deleteMainTitleImage_success',
|
|
})
|
|
|
|
}catch(error) {
|
|
console.error('deleteMainTitleImage err:', error);
|
|
}finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
exports.mgmtFunc_resetConvert = async(req, res) => {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
let { params } = req.body;
|
|
let { resourcePath, dataId } = params;
|
|
|
|
for (const data of convertingDataArr) {
|
|
if (Number(data.dataId) === Number(dataId)) {
|
|
try {
|
|
const job = await convertPdfQueue.getJob(data.jobId);
|
|
if (job) {
|
|
await job.remove();
|
|
console.log(`[Cancel] Successfully removed job ${data.jobId} from convert-pdf queue`);
|
|
}
|
|
} catch (jobErr) {
|
|
console.error('[Cancel] Error removing job:', jobErr);
|
|
}
|
|
}
|
|
}
|
|
|
|
//// 배열에서 파일 정보 삭제
|
|
convertingDataArr = convertingDataArr.filter(data => Number(data.dataId) !== Number(dataId));
|
|
|
|
const waiting = await convertPdfQueue.getWaitingCount();
|
|
const active = await convertPdfQueue.getActiveCount();
|
|
if (waiting == 0 && active == 0 && convertingDataArr.length != 0) {
|
|
convertingDataArr = [];
|
|
}
|
|
|
|
let resultData = {
|
|
message: 'mgmtFunc_resetConvert_success',
|
|
projectId: projectId,
|
|
convertingDataArr: convertingDataArr,
|
|
resourcePath: resourcePath,
|
|
dataId: dataId
|
|
};
|
|
|
|
let io = getIo();
|
|
io.emit('mgmtFunc_resetConvert_success', resultData);
|
|
|
|
res.status(200).json({
|
|
message: 'mgmtFunc_resetConvert_success',
|
|
resultData: resultData
|
|
});
|
|
}
|
|
|
|
exports.mgmtFunc_addClickLog = async (req, res) => {
|
|
try {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
let { params } = req.body;
|
|
|
|
let userIp = req.ip;
|
|
|
|
params.projectId = projectId;
|
|
params.userIp = userIp;
|
|
|
|
let insertClickLogResult = await insertClickLog(params);
|
|
if (insertClickLogResult && insertClickLogResult.message == 'insertClickLog_success') {
|
|
res.status(200).json({
|
|
message: 'insertClickLog_success',
|
|
});
|
|
} else {
|
|
res.status(200).json({
|
|
message: 'insertClickLog_failed',
|
|
});
|
|
}
|
|
} catch (err) {
|
|
console.error("exports.mgmtFunc_addClickLog err:", err);
|
|
res.status(500).json({
|
|
message: 'mgmtFunc_addClickLog_error',
|
|
error: err.message
|
|
});
|
|
}
|
|
}
|
|
|
|
exports.getControlBoxPosition = async (req, res) => {
|
|
let { userId } = req.query;
|
|
|
|
let selectControlBoxPositionResult = await selectControlBoxPosition(userId);
|
|
res.status(200).json({
|
|
message: 'getControlBoxPosition_success',
|
|
result: selectControlBoxPositionResult[0]
|
|
});
|
|
}
|
|
|
|
exports.setControlBoxPosition = async (req, res) => {
|
|
let { params } = req.body;
|
|
|
|
let updateControlBoxPositionResult = await updateControlBoxPosition(params);
|
|
if (updateControlBoxPositionResult.message == 'updateControlBoxPosition_success') {
|
|
res.status(200).json({
|
|
message: 'setControlBoxPosition_success',
|
|
});
|
|
}
|
|
}
|
|
|
|
// 🔺🔺🔺🔺🔺🔺🔺🔺 클라이언트 요청 핸들러 끝 🔺🔺🔺🔺🔺🔺🔺🔺
|
|
|
|
exports.dtest = async (req, res) => {
|
|
const client = await pool.connect();
|
|
try {
|
|
await client.query('BEGIN');
|
|
|
|
const selectQueryString = `
|
|
SELECT *
|
|
FROM ver4.${tbData}
|
|
WHERE is_folder = false
|
|
AND popup_key IS NOT NULL
|
|
AND popup_size = '0'
|
|
AND LOWER(ext) IN (
|
|
'hwp', 'hwpx', 'doc', 'docx',
|
|
'xls', 'xlsx', 'xlsm',
|
|
'ppt', 'pptx',
|
|
'dwg', 'dxf', 'grm'
|
|
);
|
|
`;
|
|
const result = await client.query(selectQueryString);
|
|
const { rows } = result;
|
|
|
|
const BATCH_SIZE = 200;
|
|
|
|
// rows를 200개씩 끊어서 처리
|
|
for (let i = 0; i < rows.length; i += BATCH_SIZE) {
|
|
const batch = rows.slice(i, i + BATCH_SIZE);
|
|
const updates = [];
|
|
|
|
// 이 배치 내에서 S3 순차 호출
|
|
for (const row of batch) {
|
|
const command = new HeadObjectCommand({
|
|
Bucket: row.bucket,
|
|
Key: row.popup_key,
|
|
});
|
|
const response = await s3.send(command);
|
|
|
|
updates.push({
|
|
dataId: row.data_id,
|
|
size: response.ContentLength,
|
|
});
|
|
}
|
|
|
|
// CASE WHEN 쿼리 생성
|
|
const caseStatements = updates
|
|
.map(u => `WHEN data_id = ${u.dataId} THEN '${u.size}'`)
|
|
.join('\n');
|
|
|
|
const idList = updates.map(u => u.dataId).join(', ');
|
|
|
|
const updateQueryString = `
|
|
UPDATE ver4.${tbData}
|
|
SET popup_size = CASE
|
|
${caseStatements}
|
|
ELSE popup_size
|
|
END
|
|
WHERE data_id IN (${idList});
|
|
`;
|
|
|
|
await client.query(updateQueryString);
|
|
}
|
|
|
|
await client.query('COMMIT');
|
|
|
|
res.status(200).json({
|
|
message: 'qwe',
|
|
});
|
|
|
|
} catch (error) {
|
|
await client.query('ROLLBACK');
|
|
console.error('qwe error:', error);
|
|
res.status(500).json({ error: 'Failed to update popup_size' });
|
|
|
|
} finally {
|
|
client.release();
|
|
}
|
|
};
|
|
exports.otest = async (req, res) => {
|
|
const client = await pool.connect();
|
|
try {
|
|
await client.query('BEGIN');
|
|
|
|
const selectQueryString = `
|
|
SELECT *
|
|
FROM ver4.tb_official_doc_file
|
|
WHERE popup_key IS NOT NULL
|
|
AND popup_size = '0'
|
|
AND LOWER(ext) IN (
|
|
'hwp', 'hwpx', 'doc', 'docx',
|
|
'xls', 'xlsx', 'xlsm',
|
|
'ppt', 'pptx',
|
|
'dwg', 'dxf', 'grm'
|
|
);
|
|
`;
|
|
const result = await client.query(selectQueryString);
|
|
const { rows } = result;
|
|
|
|
const BATCH_SIZE = 200;
|
|
|
|
// rows를 200개씩 끊어서 처리
|
|
for (let i = 0; i < rows.length; i += BATCH_SIZE) {
|
|
const batch = rows.slice(i, i + BATCH_SIZE);
|
|
const updates = [];
|
|
|
|
// 이 배치 내에서 S3 순차 호출
|
|
for (const row of batch) {
|
|
const command = new HeadObjectCommand({
|
|
Bucket: row.bucket,
|
|
Key: row.popup_key,
|
|
});
|
|
const response = await s3.send(command);
|
|
|
|
updates.push({
|
|
docId: row.doc_id,
|
|
size: response.ContentLength,
|
|
});
|
|
}
|
|
|
|
// CASE WHEN 쿼리 생성
|
|
const caseStatements = updates
|
|
.map(u => `WHEN doc_id = ${u.docId} THEN '${u.size}'`)
|
|
.join('\n');
|
|
|
|
const idList = updates.map(u => u.docId).join(', ');
|
|
|
|
const updateQueryString = `
|
|
UPDATE ver4.tb_official_doc_file
|
|
SET popup_size = CASE
|
|
${caseStatements}
|
|
ELSE popup_size
|
|
END
|
|
WHERE doc_id IN (${idList});
|
|
`;
|
|
|
|
await client.query(updateQueryString);
|
|
}
|
|
|
|
await client.query('COMMIT');
|
|
|
|
res.status(200).json({
|
|
message: 'asd',
|
|
});
|
|
|
|
} catch (error) {
|
|
await client.query('ROLLBACK');
|
|
console.error('asd error:', error);
|
|
res.status(500).json({ error: 'Failed to update popup_size' });
|
|
|
|
} finally {
|
|
client.release();
|
|
}
|
|
};
|
|
|
|
// gemini AI
|
|
exports.summarizeAI = multer({ storage: multer.memoryStorage() });
|
|
exports.summarizeAI_action = async(req, res) => {
|
|
|
|
let { projectId , objectKey, userInfoString, resourcePath, storageType, dataId, type} = req.query;
|
|
let bucket = projectId;
|
|
let userIp = req.ip;
|
|
|
|
let initiator = `DEV_LOCAL_${JSON.parse(userInfoString).user_id}`;
|
|
if (env == 'production') {
|
|
if (deploymentType == 'ONPREMISE') initiator = 'HYHC_ONPREMISE';
|
|
if (deploymentType == 'CLOUD') initiator = `AWS_CLOUD_${cloudType}`;
|
|
}
|
|
const files = req.files.prompt_file[0];
|
|
const prompt = files.buffer.toString('utf-8');
|
|
|
|
let job;
|
|
|
|
if(type == 'outer') {
|
|
|
|
// 파일 다운로드 Presigned URL
|
|
let command = new GetObjectCommand({
|
|
Bucket: bucket,
|
|
Key: objectKey,
|
|
});
|
|
|
|
let url = await getSignedUrl(s3, command, { expiresIn: 60 * 60 * 15 }); // 유효시간 : 15분
|
|
|
|
job = await summarizeAPIQueue.add(
|
|
`'${initiator}'에서 파일 AI 요약`,
|
|
{ prompt, url, resourcePath, initiator, storageType, dataId, projectId, bucket, userInfoString, userIp, type }
|
|
)
|
|
|
|
} else {
|
|
job = await summarizeAIQueue.add(
|
|
`'${initiator}'에서 파일 AI 요약`,
|
|
{ prompt, resourcePath, initiator, storageType, dataId, projectId, bucket, userInfoString, userIp, type }
|
|
)
|
|
}
|
|
|
|
//// 배열에 현재 요약중인 파일 정보 추가
|
|
summarizeAiDataArr.push({
|
|
projectId: projectId,
|
|
dataId: dataId,
|
|
resourcePath: resourcePath,
|
|
jobId: job.id,
|
|
type: type,
|
|
});
|
|
|
|
// 요약 시작 socket 전송
|
|
let resultData = { summarizeAiDataArr };
|
|
let io = getIo();
|
|
io.emit('summarize_start', resultData);
|
|
|
|
res.status(200).json({
|
|
message: 'summarizeAI_success'
|
|
});
|
|
|
|
};
|
|
|
|
|
|
// exports.summarizeAI = multer({ storage: multer.memoryStorage() });
|
|
// exports.summarizeAI_action = async(req, res) => {
|
|
|
|
// let { projectId, objectKey, storageType, userInfoString, resourcePath, dataId } = req.query;
|
|
// let bucket = projectId;
|
|
// let userIp = req.ip;
|
|
// const files = req.files.prompt_file[0];
|
|
|
|
// // 파일 다운로드 Presigned URL
|
|
// let command = new GetObjectCommand({
|
|
// Bucket: bucket,
|
|
// Key: objectKey,
|
|
// });
|
|
|
|
// let url = await getSignedUrl(s3, command, { expiresIn: 60 * 15 }); // 유효시간: 15분
|
|
|
|
// if(type == 'gemini') {
|
|
|
|
// const promptFile = files.find(f => /\.txt$/i.test(f.originalname) || f.mimetype === 'text/plain');
|
|
// const prompt = promptFile ? promptFile.buffer.toString('utf-8') : '';
|
|
|
|
// const jsonFile = files.find(f => /\.json$/i.test(f.originalname) || f.mimetype === 'application/json');
|
|
// if (jsonFile) {
|
|
// try {
|
|
// const json = JSON.parse(jsonFile.buffer.toString('utf-8'));
|
|
// } catch (e) {
|
|
// console.error('❌ JSON 파싱 실패:', e);
|
|
// return res.status(400).json({ message: 'invalid JSON file'});
|
|
// }
|
|
// }
|
|
|
|
// let initiator = `DEV_LOCAL_${JSON.parse(userInfoString).user_id}`;
|
|
// if(env == 'production') {
|
|
// if (deploymentType == 'ONPREMISE') initiator = 'HYHC_ONPREMISE';
|
|
// if (deploymentType == 'CLOUD') initiator = `AWS_CLOUD_${cloudType}`;
|
|
// };
|
|
|
|
// const job = await summarizeAIQueue.add(
|
|
// `'${initiator}'에서 PDF 파일 AI 요악`,
|
|
// { prompt, url, resourcePath, initiator, storageType, dataId, projectId, bucket, userInfoString, userIp, json }
|
|
// );
|
|
|
|
// res.status(200).json({
|
|
// jobId: job.id,
|
|
// data: job.data,
|
|
// });
|
|
|
|
// //// 배열에 현재 요약중인 파일 정보 추가
|
|
// summarizeAiDataArr.push({
|
|
// dataId: dataId,
|
|
// resourcePath: resourcePath,
|
|
// depth1,
|
|
// depth2,
|
|
// depth3,
|
|
// jobId: job.id
|
|
// });
|
|
|
|
// }else {
|
|
|
|
// const prompt = files.buffer.toString('utf-8');
|
|
|
|
// let initiator = `DEV_LOCAL_${JSON.parse(userInfoString).user_id}`;
|
|
// if (env == 'production') {
|
|
// if(deploymentType == 'ONPREMISE') initiator = 'HYHC_ONPREMISE';
|
|
// if(deploymentType == 'CLOUD') initiator = `AWS_CLOUD_${cloudType}`;
|
|
// }
|
|
|
|
// const job = await summarizeAPIQueue.add(
|
|
// `'${initiator}'에서 파일 AI 요약`,
|
|
// { prompt, url, resourcePath, initiator, storageType, dataId, projectId, bucket, userInfoString, userIp }
|
|
// );
|
|
|
|
// res.status(200).json({
|
|
// message: 'summarizeAI_success',
|
|
// });
|
|
|
|
// //// 배열에 현재 요약중인 파일 정보 추가
|
|
// summarizeAiDataArr.push({
|
|
// dataId: dataId,
|
|
// resourcePath: resourcePath
|
|
// });
|
|
// // }
|
|
|
|
// // 요약 시작 socket 전송
|
|
// let resultData = { resourcePath: resourcePath, summarizeAiDataArr };
|
|
// let io = getIo();
|
|
// io.emit('summarize_start', resultData);
|
|
|
|
// }
|
|
|
|
// ai 요약 로그 추가
|
|
exports.addSummarizeAiLog = async(req, res) => {
|
|
// let { addSummarizeAiLogParams } = req;
|
|
let { projectId, userInfoString, resourcePath, dataId, text, isState, type } = req;
|
|
let data = req;
|
|
|
|
for(let i = 0; i < summarizeAiDataArr.length; i++){
|
|
if(summarizeAiDataArr[i].dataId == dataId){
|
|
summarizeAiDataArr.splice(i, 1);
|
|
i--;
|
|
}
|
|
}
|
|
|
|
if(isState) {
|
|
let insertLogResult = await insertLog(data, 'addSummarizeAiLog');
|
|
if (insertLogResult.message == 'insertLog_success') {
|
|
// DB에 ai_summary 및 memo 텍스트 영속적 저장
|
|
const client = await pool.connect();
|
|
try {
|
|
const tbDataName = process.env.NODE_ENV === 'production' ? 'tb_data' : '_test_tb_data';
|
|
await client.query(
|
|
`UPDATE ver4.${tbDataName} SET ai_summary = $1, memo = $1 WHERE data_id = $2`,
|
|
[text, dataId]
|
|
);
|
|
} catch (dbErr) {
|
|
console.error('❌ Failed to update ai_summary/memo in DB:', dbErr);
|
|
} finally {
|
|
client.release();
|
|
}
|
|
|
|
resultData = {
|
|
message: 'addSummarizeAiLog_success',
|
|
projectId: projectId,
|
|
userInfoString: userInfoString,
|
|
resourcePath: resourcePath,
|
|
dataId: dataId,
|
|
summarizeAiDataArr: summarizeAiDataArr,
|
|
text: text,
|
|
type: type
|
|
};
|
|
}
|
|
|
|
let io = getIo();
|
|
io.emit('addSummarizeAiLog_success', resultData);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
// ai 상태 조회용
|
|
exports.summarizeState = async(req, res) => {
|
|
|
|
const { resourcePath, dataId } = req.query;
|
|
|
|
const [dataInfo] = summarizeAiDataArr.filter(
|
|
it => it.resourcePath === resourcePath && String(it.dataId) == String(dataId)
|
|
);
|
|
|
|
res.json({
|
|
message: 'summarizeState_success',
|
|
dataInfo: dataInfo
|
|
});
|
|
|
|
};
|
|
|
|
//folder download
|
|
exports.downloadzip = async(req, res)=>{
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
let bucket = projectId;
|
|
let userIp = req.ip;
|
|
let {resourcePath, userInfoString, storageType} = req.query;
|
|
|
|
let result = {};
|
|
|
|
let pathArr = resourcePath[0].split('/');
|
|
pathArr.shift();
|
|
let depth = pathArr.length;
|
|
|
|
let logParam = {
|
|
projectId:projectId,
|
|
activity : 'downloadTarget_folder',
|
|
userInfoString : userInfoString,
|
|
userIp : userIp,
|
|
resourcePathArr : resourcePath,
|
|
dataIdArr : []
|
|
};
|
|
await insertLog(logParam);
|
|
|
|
const client = await pool.connect();
|
|
|
|
try{
|
|
let queryString = `select * from ver4.${tbData}
|
|
where project_id = '${projectId}'
|
|
and is_folder = false
|
|
and is_removed = false `;
|
|
|
|
for(let i = 1; i < depth+1; i++){
|
|
queryString += ` and path${i} = '${pathArr[i-1]}' `
|
|
}
|
|
queryString += `order by path1, path2, path3`;
|
|
|
|
let {rows} = await client.query(queryString);
|
|
|
|
let folderName = pathArr.pop();
|
|
let jsonDepth = 0;
|
|
|
|
for(let i = 0 ; i < rows.length; i++){
|
|
|
|
let command = new GetObjectCommand({
|
|
Bucket : bucket,
|
|
Key : rows[i].object_key
|
|
})
|
|
let url = await getSignedUrl(s3, command, { expiresIn: 60 * 60 * 24 }); // 유효시간: 24시간
|
|
|
|
|
|
if(!result[folderName]) result[folderName] = {};
|
|
if(rows[i][`path${depth+1}`] !== null && rows[i][`path${depth+1}`] !== undefined){
|
|
if(jsonDepth < 1) jsonDepth = 1;
|
|
if(!result[folderName][rows[i][`path${depth+1}`]]) result[folderName][rows[i][`path${depth+1}`]] = {};
|
|
}
|
|
|
|
if(rows[i][`path${depth+2}`] !== null && rows[i][`path${depth+2}`] !== undefined){
|
|
if(jsonDepth < 2) jsonDepth = 2;
|
|
if(!result[folderName][rows[i][`path${depth+1}`]][rows[i][`path${depth+2}`]]) result[folderName][rows[i][`path${depth+1}`]][rows[i][`path${depth+2}`]] = {};
|
|
}else if(rows[i][`path${depth+1}`] !== null && rows[i][`path${depth+1}`] !== undefined){
|
|
result[folderName][rows[i][`path${depth+1}`]] = url;
|
|
}
|
|
|
|
if(rows[i][`path${depth+3}`] !== null && rows[i][`path${depth+3}`] !== undefined){
|
|
if(jsonDepth < 3) jsonDepth = 3;
|
|
if(!result[folderName][rows[i][`path${depth+1}`]][rows[i][`path${depth+2}`]][rows[i][`path${depth+3}`]]) result[folderName][rows[i][`path${depth+1}`]][rows[i][`path${depth+2}`]][rows[i][`path${depth+3}`]] = {};
|
|
}else if(rows[i][`path${depth+2}`] !== null && rows[i][`path${depth+2}`] !== undefined){
|
|
result[folderName][rows[i][`path${depth+1}`]][rows[i][`path${depth+2}`]] = url;
|
|
}
|
|
|
|
if(rows[i][`path${depth+4}`] !== null && rows[i][`path${depth+4}`] !== undefined){
|
|
if(jsonDepth < 4) jsonDepth = 4;
|
|
if(!result[folderName][rows[i][`path${depth+1}`]][rows[i][`path${depth+2}`]][rows[i][`path${depth+3}`]][rows[i][`path${depth+4}`]]) result[folderName][rows[i][`path${depth+1}`]][rows[i][`path${depth+2}`]][rows[i][`path${depth+3}`]][rows[i][`path${depth+4}`]] = url;
|
|
}else if(rows[i][`path${depth+3}`] !== null && rows[i][`path${depth+3}`] !== undefined){
|
|
result[folderName][rows[i][`path${depth+1}`]][rows[i][`path${depth+2}`]][rows[i][`path${depth+3}`]] = url;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
let insertQuery = `
|
|
insert into ver4.tb_download_folder (user_id, project_id, path, name)
|
|
values ($1, $2, $3, $4)
|
|
`
|
|
await client.query(insertQuery, [JSON.parse(userInfoString).user_id, projectId, resourcePath[0], folderName]);
|
|
|
|
}catch(err){
|
|
console.error('downloadZip query 중 error 발생: '+err);
|
|
}finally{
|
|
client.release();
|
|
}
|
|
|
|
|
|
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 zipFolderQueue.add(
|
|
`'${initiator}'에서 download 요청(zip생성)`,{
|
|
json : result,
|
|
userInfoString : userInfoString,
|
|
resourcePath: resourcePath,
|
|
bucket : projectId,
|
|
storageType : storageType,
|
|
initiator : initiator,
|
|
}
|
|
);
|
|
|
|
res.status(200).json({
|
|
jobId: job.id,
|
|
});
|
|
}
|
|
|
|
exports.getMyDownloadList = async(req, res) => {
|
|
const { user_id } = req.query;
|
|
const client = await pool.connect();
|
|
try{
|
|
let queryString = `
|
|
select *, (select project_nm from ver4.tb_project where project_id = ver4.tb_download_folder.project_id) project_nm from ver4.tb_download_folder where user_id = $1 and (expire_date > now() - interval '1 day' OR made = false) order by expire_date
|
|
`
|
|
let result = await client.query(queryString, [user_id]);
|
|
res.status(200).json(result.rows);
|
|
}catch(error){
|
|
console.error('getMyDownloadList query 중 error 발생: '+error);
|
|
res.status(500).json({ error: error.message, message: 'getMyDownloadList_failed' });
|
|
}finally{
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
function parseJsonToFlatArray(jsonData) {
|
|
const fileList = [];
|
|
|
|
function traverse(node, currentPath) {
|
|
for (const key in node) {
|
|
const value = node[key];
|
|
|
|
if (typeof value === 'object' && value !== null) {
|
|
const nextPath = `${currentPath}/${key}`;
|
|
traverse(value, nextPath);
|
|
} else if (typeof value === 'string') {
|
|
fileList.push({
|
|
filename: key,
|
|
path: currentPath,
|
|
url: value
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
if (jsonData && typeof jsonData === 'object') {
|
|
traverse(jsonData, '');
|
|
} else {
|
|
console.error("유효한 JSON 데이터 객체가 아닙니다.");
|
|
}
|
|
|
|
return fileList;
|
|
}
|
|
|
|
// 유저 권한 로그 추가
|
|
exports.addPermissionLog = async (req, res) => {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
let { logs } = req.body;
|
|
// logs안에 insertLog에서 쓰는 params에 형태로 그대로 들어있어 req.body에서 꺼낸 후 for문으로 내부함수 insertLog 호출
|
|
try {
|
|
for(let i = 0; logs.length > i; i++){
|
|
await insertLog(logs[i]);
|
|
}
|
|
// 로그 삽입 완료 이후 이벤트 클라이언트 소켓에 전달
|
|
let resultData = {
|
|
message: 'addPermissionLog_success',
|
|
projectId: projectId,
|
|
};
|
|
let io = getIo();
|
|
io.emit('addPermissionLog_success', resultData);
|
|
|
|
res.status(200).json({message : 'addPermissionLog success'});
|
|
} catch(err) {
|
|
res.status(500).json({message : 'addPermissionLog failed'});
|
|
console.error('addPermissionLog Error', err);
|
|
}
|
|
}
|
|
|
|
// 이전 파일 썸네일 추가 (임시)
|
|
exports.getNullThumbnailDataInfo = async(req, res) => {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
|
|
let result = await getNullThumbnailDataInfoAction(projectId);
|
|
|
|
res.status(200).json({
|
|
message: 'getNullThumbnailDataInfo_success',
|
|
result: result,
|
|
});
|
|
|
|
}
|
|
|
|
// 이전 파일 썸네일 추가 -select
|
|
async function getNullThumbnailDataInfoAction(projectId) {
|
|
let bucket = projectId;
|
|
|
|
const client = await pool.connect();
|
|
try {
|
|
|
|
// let queryString = `
|
|
// SELECT DISTINCT f.*
|
|
// FROM ver4.${tbData} as f
|
|
// JOIN ver4.${tbData} as g
|
|
// ON g.project_id = f.project_id
|
|
// AND g.bucket = f.bucket
|
|
// AND g.data_depth = 3
|
|
// AND g.folder_type = 'gallery'
|
|
// AND g.path1 = f.path1
|
|
// AND g.path2 = f.path2
|
|
// AND g.path3 = f.path3
|
|
// WHERE f.project_id = '${projectId}'
|
|
// AND f.bucket = '${bucket}'
|
|
// AND f.is_folder = false
|
|
// AND f.is_folder = false
|
|
// AND f.is_removed = false
|
|
// AND f.data_depth = 4
|
|
// AND f.ext in ('png', 'jpg', 'jpeg', 'webp', 'gif', 'mp4', 'webm', 'mov')
|
|
// AND f.thumbnail_key is null;
|
|
// `
|
|
|
|
let queryString = `
|
|
SELECT f.*
|
|
FROM ver4.${tbData} as f
|
|
WHERE f.project_id = $1
|
|
AND f.is_folder = false
|
|
AND f.is_removed = false
|
|
AND f.data_depth >= 4
|
|
AND f.ext in ('png', 'jpg', 'jpeg', 'webp', 'mp4', 'webm', 'mov', 'pdf', 'hwp', 'hwpx', 'doc', 'docx', 'xls', 'xlsx', 'xlsm', 'ppt', 'pptx', 'dwg', 'dxf', 'grm', 'txt', 'md', 'gif')
|
|
AND f.preview_key is not null
|
|
AND f.popup_key is not null
|
|
AND f.thumbnail_key is null;
|
|
`;
|
|
|
|
// let queryString = `
|
|
// SELECT f.*
|
|
// FROM ver4.${tbData} as f
|
|
// WHERE f.project_id = $1
|
|
// AND f.bucket = $2
|
|
// AND f.is_folder = false
|
|
// AND f.is_removed = false
|
|
// AND f.data_depth = 4
|
|
// AND f.ext in ('png', 'jpg', 'jpeg', 'webp', 'gif', 'mp4', 'webm', 'mov', 'pdf')
|
|
// AND f.thumbnail_key is null;
|
|
// `;
|
|
|
|
let values = [projectId];
|
|
let { rows } = await client.query(queryString, values)
|
|
|
|
// let { rows } = await client.query(queryString, [projectId, bucket]);
|
|
return rows;
|
|
}catch(err) {
|
|
console.error('getNullThumbnailDataInfoAction err:', err)
|
|
}finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
// 이전 파일 썸네일 추가 - update
|
|
exports.updateThumbnailInfo = async (req, res) => {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
|
|
let result = await updateThumbnailInfoAction(projectId, req.body);
|
|
|
|
res.status(200).json(result);
|
|
}
|
|
|
|
async function updateThumbnailInfoAction(projectId, params) {
|
|
let bucket = projectId;
|
|
const client = await pool.connect();
|
|
|
|
try {
|
|
const {
|
|
data_id,
|
|
thumbnail_key,
|
|
thumbnail_size,
|
|
lon,
|
|
lat,
|
|
height,
|
|
mod_user_id
|
|
} = params;
|
|
|
|
let queryString = `
|
|
UPDATE ver4.${tbData}
|
|
SET
|
|
thumbnail_key = $1,
|
|
thumbnail_size = $2,
|
|
lon = $3,
|
|
lat = $4,
|
|
height = $5,
|
|
mod_user_id = $6
|
|
WHERE data_id = $7
|
|
AND project_id = $8
|
|
AND bucket = $9
|
|
RETURNING data_id, thumbnail_key, thumbnail_size;
|
|
`;
|
|
|
|
let values = [thumbnail_key, thumbnail_size, lon, lat, height, mod_user_id, data_id, projectId, bucket];
|
|
|
|
let { rows } = await client.query(queryString, values);
|
|
|
|
if (rows.length > 0) {
|
|
return {
|
|
message: 'updateThumbnailInfo_success',
|
|
result: rows[0],
|
|
}
|
|
}else {
|
|
return { message: 'updateThumbnailInfo_not_found' };
|
|
}
|
|
}catch(err) {
|
|
console.error('updateThumbnailInfoAction err:', err);
|
|
return {
|
|
message: 'updateThumbnailInfo_error',
|
|
error: err.message
|
|
};
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
|
|
// 삭제예정(2025.10.31): 이호성
|
|
// exports.get3dViewerThumbUrl = async(req, res, next) => {
|
|
// const dataId = req.query.dataId;
|
|
|
|
// const client = await pool.connect();
|
|
// try {
|
|
// let queryString = `
|
|
// SELECT t.*
|
|
// FROM ver4.${tbData} as t
|
|
// WHERE t.data_id = $1
|
|
// `;
|
|
// let values = [dataId];
|
|
// let { rows } = await client.query(queryString, values);
|
|
|
|
// if (!rows.length) {
|
|
// return res.status(404).json({ message: 'Not found', dataId });
|
|
// }
|
|
|
|
// const { thumbnail_key, path1, path2, path3, path4, path5 } = rows[0];
|
|
|
|
// let resourcePath = path.posix.join(path1, path2, path3, path4);
|
|
// if (path5) resourcePath = resourcePath + '/' + path5;
|
|
|
|
// const basename = path.posix.basename(resourcePath);
|
|
// const dirname = path.posix.dirname(resourcePath);
|
|
// const fileNameWithoutExt = path.posix.parse(basename).name;
|
|
// const thumbnailPath = path.posix.join(dirname, `${fileNameWithoutExt}.jpeg`);
|
|
|
|
// res.json({ thumbnail_key, resourcePath, thumbnailPath });
|
|
// } catch(err) {
|
|
// console.error('get3dViewerThumbUrl err:', err)
|
|
// next(err)
|
|
// } finally {
|
|
// client.release();
|
|
// }
|
|
// }
|
|
|