1362 lines
46 KiB
JavaScript
1362 lines
46 KiB
JavaScript
const pool = require("../db/pool.js");
|
|
const { getIo } = require('../socket');
|
|
const dotenv = require('dotenv');
|
|
dotenv.config();
|
|
|
|
//// s3 api 관련
|
|
const { getSignedUrl } = require('@aws-sdk/s3-request-presigner');
|
|
const {
|
|
DeleteObjectCommand,
|
|
PutObjectCommand,
|
|
GetObjectCommand,
|
|
} = require('@aws-sdk/client-s3');
|
|
|
|
//// 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 tbOfficialDocFile = env == 'production' ? 'tb_official_doc_file' : 'tb_official_doc_file';
|
|
const tbOfficialDocCompany = env == 'production' ? 'tb_official_doc_company' : 'tb_official_doc_company';
|
|
|
|
//// 큐 관련
|
|
const {
|
|
convertPdfQueue,
|
|
} = require('../queue.js'); // queue.js에서 행동별 Queue 객체 생성 후 사용
|
|
|
|
//// 변환 필요 확장자, 변환 불필요 확장자, 지원여부 확장자
|
|
// -> pdf 암호화 안하는 버전 - 변환 불필요 확장자에 pdf 포함
|
|
let needConvertExtArr = ['hwp', 'hwpx', 'doc', 'docx', 'xls', 'xlsx', 'xlsm', 'ppt', 'pptx', 'dwg', 'dxf', 'grm'];
|
|
let notNeedConvertExtArr = ['pdf', 'ifc', 'gsim', 'mp4', 'webm', 'jpg', 'jpeg', 'png', 'txt', 'log', 'md', 'url', 'zip'];
|
|
// -> 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 convertingDocPathArr = [];
|
|
|
|
/************************************************************************************************************************* 유틸리티 함수 */
|
|
// storageType 에 따라 클라이언트 리턴
|
|
function getBasePrefix(pageType) {
|
|
if (pageType == null || pageType == undefined) {
|
|
pageType = 'officialDoc';
|
|
}
|
|
return {
|
|
origin: `${pageType}/origin/`,
|
|
pdf: `${pageType}/pdf/`,
|
|
pdf_thumb: `${pageType}/pdf_thumb/`,
|
|
};
|
|
}
|
|
|
|
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;
|
|
|
|
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}`;
|
|
}
|
|
|
|
/************************************************************************************************************************* DB 관련 함수 */
|
|
exports.getDocSeq = async (req, res, next) => {
|
|
const client = await pool.connect();
|
|
|
|
try {
|
|
let queryString = `SELECT nextval('ver4.seq_tb_official_doc_file'::regclass)`;
|
|
const result = await client.query(queryString);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: '200',
|
|
data: result.rows[0].nextval,
|
|
});
|
|
} catch (error) {
|
|
console.error('getDocSeq error: ', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '500',
|
|
error: error.message,
|
|
});
|
|
} finally {
|
|
client.release();
|
|
}
|
|
};
|
|
|
|
exports.getDocData = async (req, res, next) => {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
const label = req.query.label;
|
|
const client = await pool.connect();
|
|
|
|
try {
|
|
let queryString = `
|
|
SELECT
|
|
ROW_NUMBER() OVER (PARTITION BY doc_direction ORDER BY doc_id DESC) as rownum,
|
|
doc.doc_id,
|
|
doc.project_id,
|
|
doc.uploader,
|
|
doc.create_date,
|
|
doc."permission",
|
|
doc.bucket,
|
|
doc.file_path,
|
|
doc.ext,
|
|
doc.doc_direction,
|
|
doc.doc_number,
|
|
doc.doc_date,
|
|
doc.recipient_org,
|
|
doc.recipient_org_abbr,
|
|
doc.recipient_name,
|
|
doc.recipient_name_abbr,
|
|
doc.sender_org,
|
|
doc.sender_org_abbr,
|
|
doc.sender_name,
|
|
doc.sender_name_abbr,
|
|
doc.doc_title,
|
|
doc.doc_title_summary,
|
|
doc.doc_content_summary,
|
|
doc.doc_related_docs,
|
|
doc.doc_type,
|
|
doc.doc_category,
|
|
doc.attachment_title,
|
|
doc.attachment_count,
|
|
doc.doc_memo,
|
|
doc.doc_manager,
|
|
doc.doc_label,
|
|
doc.mod_date,
|
|
doc.mod_user_id,
|
|
doc.mod_activity,
|
|
doc.data_size,
|
|
doc.storage_type,
|
|
doc.object_key,
|
|
doc.preview_key,
|
|
doc.popup_key,
|
|
doc.ver,
|
|
doc.group_id,
|
|
doc.is_aiused
|
|
FROM ver4.${tbOfficialDocFile} doc
|
|
WHERE project_id = $1
|
|
`;
|
|
|
|
const values = [projectId];
|
|
|
|
if (label) {
|
|
queryString += ` AND doc.doc_label = $2`;
|
|
values.push(label);
|
|
}
|
|
|
|
queryString += ` ORDER BY doc.doc_id`;
|
|
|
|
const result = await client.query(queryString, values);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'getDocData_success',
|
|
data: result.rows,
|
|
});
|
|
} catch (error) {
|
|
console.error('getDocData error: ', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '500',
|
|
error: error.message,
|
|
});
|
|
} finally {
|
|
client.release();
|
|
}
|
|
};
|
|
|
|
exports.getDocDataBySelected = async (req, res, next) => {
|
|
const client = await pool.connect();
|
|
const { projectId, companyType, base, target, category, storageType, direction } = req.query;
|
|
const docCategory = category;
|
|
const docDirection = direction;
|
|
|
|
try {
|
|
// 카운트 쿼리
|
|
let selectCount = `
|
|
SELECT doc.doc_category, count(*)
|
|
`;
|
|
|
|
// 리스트 쿼리
|
|
let selectList = `
|
|
SELECT
|
|
ROW_NUMBER() OVER (PARTITION BY doc_direction ORDER BY doc_id) as rownum,
|
|
doc.doc_id,
|
|
doc.project_id,
|
|
doc.uploader,
|
|
doc.create_date,
|
|
doc."permission",
|
|
doc.bucket,
|
|
doc.file_path,
|
|
doc.ext,
|
|
doc.doc_direction,
|
|
doc.doc_number,
|
|
doc.doc_date,
|
|
doc.recipient_org,
|
|
doc.recipient_org_abbr,
|
|
doc.recipient_name,
|
|
doc.recipient_name_abbr,
|
|
doc.sender_org,
|
|
doc.sender_org_abbr,
|
|
doc.sender_name,
|
|
doc.sender_name_abbr,
|
|
doc.doc_title,
|
|
doc.doc_title_summary,
|
|
doc.doc_content_summary,
|
|
doc.doc_related_docs,
|
|
doc.doc_type,
|
|
doc.doc_category,
|
|
doc.attachment_title,
|
|
doc.attachment_count,
|
|
doc.doc_memo,
|
|
doc.doc_manager,
|
|
doc.doc_label,
|
|
doc.mod_date,
|
|
doc.mod_user_id,
|
|
doc.mod_activity,
|
|
doc.data_size,
|
|
doc.storage_type,
|
|
doc.object_key,
|
|
doc.preview_key,
|
|
doc.popup_key,
|
|
doc.ver,
|
|
doc.group_id,
|
|
doc.is_aiused,
|
|
base.company_name AS base,
|
|
target.company_name AS target
|
|
`;
|
|
|
|
let queryString = `
|
|
FROM ver4.${tbOfficialDocFile} doc
|
|
JOIN ver4.${tbOfficialDocCompany} base
|
|
ON doc.project_id = base.project_id
|
|
AND base.company_type = $1
|
|
AND base.company_role = '기준'
|
|
AND base.company_name = $2
|
|
JOIN ver4.${tbOfficialDocCompany} target
|
|
ON doc.project_id = target.project_id
|
|
AND target.company_type = $1
|
|
AND target.company_role = '상대기관'
|
|
AND target.company_name = $3
|
|
WHERE doc.project_id = $4
|
|
AND (
|
|
(doc.recipient_org_abbr = base.company_name AND doc.sender_org_abbr = target.company_name)
|
|
OR
|
|
(doc.recipient_org_abbr = target.company_name AND doc.sender_org_abbr = base.company_name)
|
|
)
|
|
AND doc.bucket = $4
|
|
AND doc.storage_type = $5
|
|
`;
|
|
|
|
// 카운트 쿼리
|
|
let groupBy = `
|
|
GROUP BY doc.doc_category
|
|
`;
|
|
|
|
// 리스트 쿼리 //
|
|
let orderBy = `
|
|
ORDER BY doc.doc_id DESC
|
|
`;
|
|
|
|
// 카운트 쿼리 조합
|
|
let countQuery = selectCount + queryString + groupBy;
|
|
let countParams = [companyType, base, target, projectId, storageType];
|
|
const countResult = await client.query(countQuery, countParams);
|
|
|
|
let listQuery = selectList + queryString;
|
|
let listParams = [companyType, base, target, projectId, storageType];
|
|
|
|
let paramIndex = 6;
|
|
|
|
// 1) category 있을 때만
|
|
if (docCategory && docCategory !== '') {
|
|
listQuery += ` AND doc.doc_category = $${paramIndex}`;
|
|
listParams.push(docCategory);
|
|
paramIndex++;
|
|
}
|
|
|
|
// 2) direction 있을 때만(수/발신)
|
|
if (docDirection && docDirection !== '') {
|
|
listQuery += ` AND doc.doc_direction = $${paramIndex}`;
|
|
listParams.push(docDirection);
|
|
paramIndex++;
|
|
}
|
|
|
|
// 3) ORDER BY
|
|
listQuery += orderBy;
|
|
|
|
const listResult = await client.query(listQuery, listParams);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: '200',
|
|
countData: countResult.rows,
|
|
listData: listResult.rows,
|
|
});
|
|
} catch (error) {
|
|
console.error('getData error: ', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '500',
|
|
error: error.message,
|
|
});
|
|
} finally {
|
|
client.release();
|
|
}
|
|
};
|
|
|
|
// 설정모달 회사리스트 가져오기
|
|
exports.getCompanyList = async (req, res, next) => {
|
|
const client = await pool.connect();
|
|
const projectId = req.params.projectId;
|
|
try {
|
|
let queryString = `
|
|
SELECT
|
|
project_id,
|
|
company_name,
|
|
company_type,
|
|
company_role,
|
|
company_id
|
|
FROM ver4.${tbOfficialDocCompany}
|
|
WHERE project_id = $1
|
|
ORDER BY company_type, company_role
|
|
`;
|
|
const result = await client.query(queryString, [projectId]);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: '200',
|
|
data: result.rows,
|
|
});
|
|
} catch (error) {
|
|
console.error('getCompanyList error', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '500',
|
|
error: error.message,
|
|
});
|
|
} finally {
|
|
client.release();
|
|
}
|
|
};
|
|
|
|
// 셀렉트 박스 회사리스트
|
|
exports.getGroupCompanyData = async (req, res) => {
|
|
const client = await pool.connect();
|
|
const projectId = req.params.projectId;
|
|
|
|
try {
|
|
const query = `
|
|
SELECT
|
|
project_id,
|
|
company_name,
|
|
company_type,
|
|
company_role,
|
|
company_id
|
|
FROM ver4.${tbOfficialDocCompany}
|
|
WHERE project_id = $1
|
|
ORDER BY company_type
|
|
`;
|
|
const result = await client.query(query, [projectId]);
|
|
const companyList = result.rows;
|
|
|
|
// 그룹핑
|
|
const grouped = {};
|
|
|
|
companyList.forEach((item) => {
|
|
const type = item.company_type; // 발주처 / 발주처외
|
|
const role = item.company_role; // 기준 / 상대기관
|
|
const name = item.company_name;
|
|
|
|
if (!grouped[type]) grouped[type] = { 기준: [], 상대기관: [] };
|
|
|
|
grouped[type][role].push({ name, id: item.company_id });
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
message: '200',
|
|
data: grouped,
|
|
});
|
|
} catch (error) {
|
|
console.error('getGroupCompanyData error', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '500',
|
|
error: error.message,
|
|
});
|
|
} finally {
|
|
client.release();
|
|
}
|
|
};
|
|
|
|
// 설정모달 저장버튼 : 회사 관련
|
|
exports.saveCompanyList = async (req, res, next) => {
|
|
const client = await pool.connect();
|
|
const deletedCompanyList = req.body.deletedCompanyList || [];
|
|
const companyList = req.body.companyList;
|
|
try {
|
|
// 삭제 먼저
|
|
for (let i = 0; i < deletedCompanyList.length; i++) {
|
|
const item = deletedCompanyList[i];
|
|
const projectId = item.project_id;
|
|
const companyId = item.company_id;
|
|
|
|
const deleteQuery = `
|
|
DELETE FROM ver4.${tbOfficialDocCompany}
|
|
WHERE TRIM(project_id) = $1 AND company_id = $2
|
|
`;
|
|
const deleteValues = [projectId, companyId];
|
|
|
|
await client.query(deleteQuery, deleteValues);
|
|
}
|
|
|
|
// 그다음 수정추가 반영
|
|
for (let i = 0; i < companyList.length; i++) {
|
|
const item = companyList[i];
|
|
const projectId = item.project_id;
|
|
const companyId = item.company_id;
|
|
const companyName = item.company_name;
|
|
const companyType = item.company_type;
|
|
const companyRole = item.company_role;
|
|
|
|
if (!companyId || companyId === 'undefined' || companyId === 'null' || companyId.trim() === '') {
|
|
// 새로 추가
|
|
const insertQuery = `
|
|
INSERT INTO ver4.${tbOfficialDocCompany}
|
|
(project_id, company_name, company_type, company_role)
|
|
VALUES ($1, $2, $3, $4)
|
|
`;
|
|
const insertValues = [projectId, companyName, companyType, companyRole];
|
|
await client.query(insertQuery, insertValues);
|
|
} else {
|
|
// 수정 or 기존값 존재
|
|
const updateQuery = `
|
|
INSERT INTO ver4.${tbOfficialDocCompany}
|
|
(project_id, company_id, company_name, company_type, company_role)
|
|
VALUES ($1, $2, $3, $4, $5)
|
|
ON CONFLICT (project_id, company_id)
|
|
DO UPDATE SET
|
|
company_name = EXCLUDED.company_name,
|
|
company_type = EXCLUDED.company_type,
|
|
company_role = EXCLUDED.company_role
|
|
`;
|
|
const updateValues = [projectId, companyId, companyName, companyType, companyRole];
|
|
await client.query(updateQuery, updateValues);
|
|
}
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
message: '200',
|
|
});
|
|
} catch (error) {
|
|
console.error('saveCompanyList error', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '500',
|
|
error: error.message,
|
|
});
|
|
} finally {
|
|
client.release();
|
|
}
|
|
};
|
|
|
|
// 설정모달 저장버튼 : 사용자 설정 관련
|
|
exports.saveUserSetting = async (req, res, next) => {
|
|
const client = await pool.connect();
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
const userInfo = req.body.userInfo ? JSON.parse(req.body.userInfo) : undefined;
|
|
let userId = (userInfo) ? userInfo.user_id : undefined;
|
|
const { isInstructionsChecked } = req.body;
|
|
if (!userId) return;
|
|
|
|
try {
|
|
const queryString = `
|
|
INSERT INTO ver4.tb_user_setting
|
|
(project_id, user_id, doc_option_instructions)
|
|
VALUES($1, $2, $3)
|
|
ON CONFLICT (project_id, user_id)
|
|
DO UPDATE SET
|
|
doc_option_instructions = EXCLUDED.doc_option_instructions;
|
|
`;
|
|
|
|
const values = [projectId, userId, isInstructionsChecked];
|
|
|
|
await client.query(queryString, values);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'setting_saved',
|
|
});
|
|
} catch (error) {
|
|
console.error('saveUserSetting error: ', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '500',
|
|
error: error.message,
|
|
});
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
exports.getUserSetting = async (req, res, next) => {
|
|
const client = await pool.connect();
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
const userInfo = req.query.userInfoString ? JSON.parse(req.query.userInfoString) : undefined;
|
|
let userId = (userInfo) ? userInfo.user_id : undefined;
|
|
if (!userId) return;
|
|
|
|
try {
|
|
const queryString = `
|
|
SELECT doc_option_instructions, doc_option_summary
|
|
FROM ver4.tb_user_setting
|
|
WHERE project_id = $1 AND user_id = $2
|
|
`;
|
|
|
|
const values = [projectId, userId];
|
|
|
|
const result = await client.query(queryString, values);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: result.rows[0],
|
|
});
|
|
} catch (error) {
|
|
console.error('getUserSetting error: ', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '500',
|
|
error: error.message,
|
|
});
|
|
} finally {
|
|
client.release();
|
|
}
|
|
};
|
|
|
|
/************************************************************************************************************************* 비즈니스 로직 함수 */
|
|
exports.uploadDocData = async (req, res, next) => {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
const { mode, params } = req.body;
|
|
params.projectId = projectId;
|
|
|
|
try {
|
|
if (mode === 'modal_add') {
|
|
// 모달 추가모드 (파일 1개)
|
|
const result = await insertDocRows(params, req);
|
|
if (result !== 'insertDocRows_success') throw new Error('DB 저장 실패');
|
|
return res.status(200).json({ message: 'modal_add_success' });
|
|
} else if (mode === 'modal_edit') {
|
|
// 모달 수정모드 (파일 없음)
|
|
const result = await updateDocRows(params, req);
|
|
if (result !== 'updateDocRows_success') throw new Error('DB 수정 실패');
|
|
return res.status(200).json({ message: 'modal_edit_success' });
|
|
} else if (mode === 'add_attach') {
|
|
// 일반 첨부파일 추가 (멀티파일)
|
|
const result = await insertDocRows(params, req);
|
|
if (result !== 'insertDocRows_success') throw new Error('DB 저장 실패');
|
|
return res.status(200).json({ message: 'add_attach_success' });
|
|
} else if (mode === 'exist') {
|
|
// 중복파일
|
|
const result = await updateExistRows(params, req);
|
|
if (result !== 'updateExistRows_success') throw new Error('DB 저장 실패');
|
|
return res.status(200).json({ message: 'updateExistRows_success' });
|
|
}
|
|
else {
|
|
return res.status(400).json({ message: 'Invalid mode' });
|
|
}
|
|
} catch (error) {
|
|
console.error('uploadDocData error:', error);
|
|
next(error);
|
|
}
|
|
};
|
|
|
|
exports.generateUploadDocUrl = async (req, res, next) => {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
let pageType;
|
|
|
|
let { resourcePath, date } = req.body;
|
|
let bucket = projectId;
|
|
|
|
let fullPath = getBasePrefix(pageType).origin + resourcePath;
|
|
fullPath = fullPath.replaceAll('\\', '/');
|
|
fullPath = fullPath.replaceAll('//', '/');
|
|
|
|
let objectKey = `${fullPath}__${makeObjectKeyTimestamp(date)}`;
|
|
let url;
|
|
try {
|
|
// S3 명령어 구성
|
|
const command = new PutObjectCommand({
|
|
Bucket: bucket,
|
|
Key: objectKey,
|
|
ContentType: 'application/octet-stream',
|
|
});
|
|
|
|
// Presigned URL 생성 옵션
|
|
// - expiresIn: 유효 기간 (초 단위, 여기선 5분)
|
|
url = await getSignedUrl(s3, command, { expiresIn: 60 * 5 });
|
|
} catch (error) {
|
|
console.error('❌ Presigned URL 생성 실패:', error);
|
|
}
|
|
|
|
// 클라이언트에 Presigned URL, 키, 메타데이터 반환
|
|
res.status(200).json({
|
|
message: 'generateUploadDocUrl_success',
|
|
result: {
|
|
url: url,
|
|
objectKey: objectKey,
|
|
date: date,
|
|
},
|
|
});
|
|
};
|
|
|
|
exports.generateGetDocUrl = async (req, res, next) => {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
let { objectKey } = req.query;
|
|
let bucket = projectId;
|
|
|
|
let url;
|
|
try {
|
|
const command = new GetObjectCommand({
|
|
Bucket: bucket,
|
|
Key: objectKey,
|
|
});
|
|
|
|
url = await getSignedUrl(s3, command, { expiresIn: 60 });
|
|
} catch (error) {
|
|
console.error('❌ Presigned URL 가져오기 실패:', error);
|
|
}
|
|
|
|
res.status(200).json({
|
|
message: 'generateGetDocUrl_success',
|
|
result: {
|
|
url: url,
|
|
objectKey: objectKey,
|
|
},
|
|
});
|
|
};
|
|
|
|
exports.generateDownloadDocUrl = async (req, res, next) => {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
let { objectKey, resourcePath } = req.body;
|
|
let bucket = projectId;
|
|
let fileName = resourcePath.split('/').pop();
|
|
try {
|
|
let command = new GetObjectCommand({
|
|
Bucket: bucket,
|
|
Key: objectKey,
|
|
// 아래 두 옵션을 넣으면 브라우저가 파일을 바로 열지 않고 다운로드로 처리하도록 서버 응답 헤더를 강제설정할 수 있음:
|
|
ResponseContentDisposition: `attachment; filename="${encodeURIComponent(fileName)}"`,
|
|
ResponseCacheControl: 'no-cache',
|
|
});
|
|
|
|
let url = await getSignedUrl(s3, command, { expiresIn: 60 * 5 }); // 5분 유효
|
|
|
|
res.status(200).json({
|
|
message: 'generateDownloadDocUrl_success',
|
|
url: url
|
|
});
|
|
} catch (error) {
|
|
console.error('❌ Download Presigned URL 생성 실패:', error);
|
|
}
|
|
}
|
|
|
|
exports.generateDeleteDocUrl = async (req, res, next) => {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
let { objectKey } = req.body;
|
|
let bucket = projectId;
|
|
|
|
let url;
|
|
try {
|
|
const command = new DeleteObjectCommand({
|
|
Bucket: bucket,
|
|
Key: objectKey,
|
|
});
|
|
|
|
url = await getSignedUrl(s3, command, { expiresIn: 60 });
|
|
} catch (error) {
|
|
console.error('❌ Presigned URL 삭제 생성 실패:', error);
|
|
}
|
|
|
|
res.status(200).json({
|
|
message: 'generateDeleteDocUrl_success',
|
|
result: {
|
|
url: url,
|
|
objectKey: objectKey,
|
|
},
|
|
});
|
|
};
|
|
|
|
exports.deleteDocData = async (req, res, next) => {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
const { docId, groupId, objectKey, isExists } = req.body;
|
|
const bucket = projectId;
|
|
|
|
try {
|
|
// 공문파일인지 첨부파일인지 확인
|
|
let isOfficialDoc = !objectKey.includes('__attachment/');
|
|
|
|
// 공문파일 삭제일 때만 모든 첨부파일을 삭제
|
|
let deleteKeyArr = [objectKey];
|
|
|
|
// 중복파일인 경우에는 첨부파일 삭제 X, DB 삭제 X
|
|
if (isExists) {
|
|
// 중복파일이므로 MinIO에서 해당 파일 삭제는 해야함
|
|
const deleteCommand = new DeleteObjectCommand({
|
|
Bucket: bucket,
|
|
Key: objectKey,
|
|
});
|
|
await s3.send(deleteCommand);
|
|
|
|
// 첨부파일 삭제는 건너뛰고 바로 성공 응답
|
|
return res.status(200).json({ message: 'exist_file_delete_success' });
|
|
}
|
|
|
|
if (!isOfficialDoc) {
|
|
// 첨부파일의 경우
|
|
const attachmentKeys = await getAttachmentObjectKeys(projectId, groupId);
|
|
attachmentKeys.forEach(item => {
|
|
if (item.object_key === objectKey && item.popup_key !== null) {
|
|
deleteKeyArr.push(item.popup_key);
|
|
}
|
|
});
|
|
} else {
|
|
// 공문파일의 경우
|
|
const attachmentKeys = await getAttachmentObjectKeys(projectId, groupId);
|
|
attachmentKeys.forEach(item => {
|
|
if (item.object_key !== objectKey) {
|
|
deleteKeyArr.push(item.object_key); // 첨부파일 object_key 추가
|
|
if (item.popup_key !== null) {
|
|
deleteKeyArr.push(item.popup_key); // popup_key 있으면 추가
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// 1. DB 삭제
|
|
const deleteDocRowsRes = await deleteDocRows(projectId, docId, groupId, isOfficialDoc);
|
|
if (!deleteDocRowsRes.success) {
|
|
return res.status(400).json({ message: 'db_delete_failed', detail: dbResult.message });
|
|
}
|
|
|
|
// 2. MinIO 파일 삭제
|
|
for (let key of deleteKeyArr) {
|
|
const deleteCommand = new DeleteObjectCommand({
|
|
Bucket: bucket,
|
|
Key: key,
|
|
});
|
|
await s3.send(deleteCommand);
|
|
}
|
|
|
|
// 성공 응답
|
|
return res.status(200).json({ message: 'delete_success' });
|
|
} catch (error) {
|
|
console.error('❌ deleteDocData error:', error);
|
|
return res.status(500).json({ message: 'delete_failed', error: error.message });
|
|
}
|
|
};
|
|
|
|
exports.renameDocTarget = async(req, res) => {
|
|
let { params } = req.body;
|
|
let { newPath, userInfoString, docId, storageType } = params;
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
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 queryString = `
|
|
UPDATE ver4.tb_official_doc_file
|
|
SET
|
|
file_path = $1,
|
|
mod_user_id = $2,
|
|
mod_date = $3
|
|
WHERE doc_id = $4
|
|
AND project_id = $5
|
|
AND bucket = $6
|
|
AND storage_type = $7;
|
|
`;
|
|
const result = await client.query(queryString, [newPath, userId, dateNow, docId, projectId, bucket, storageType ]);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'renameDocTarget_success',
|
|
data: result.rows,
|
|
});
|
|
} catch (error) {
|
|
console.error('renameDocTarget error', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: '500',
|
|
error: error.message,
|
|
});
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
async function insertDocRows(params) {
|
|
const client = await pool.connect();
|
|
|
|
let result;
|
|
|
|
let projectId = params.projectId;
|
|
let userInfo = params.userInfoString ? JSON.parse(params.userInfoString) : undefined;
|
|
let storageType = params.uploadParams?.storageType;
|
|
let dateArr = params.uploadParams?.dateArr;
|
|
let resourcePathArr = params.uploadParams?.resourcePathArr;
|
|
let sizeArr = params.uploadParams?.sizeArr;
|
|
let objectKeyArr = params.uploadParams?.objectKeyArr;
|
|
|
|
let bucket = projectId;
|
|
let userId = (userInfo) ? userInfo.user_id : undefined;
|
|
if (!userId) return;
|
|
|
|
let {
|
|
doc_id,
|
|
doc_direction, doc_number, doc_date,
|
|
recipient_org, recipient_org_abbr, recipient_name, recipient_name_abbr,
|
|
sender_org, sender_org_abbr, sender_name, sender_name_abbr,
|
|
doc_title, doc_title_summary, doc_content_summary, doc_related_docs,
|
|
doc_type, doc_category,
|
|
attachment_title, attachment_count, doc_memo, doc_manager, doc_label,
|
|
group_id, is_aiused
|
|
} = params;
|
|
|
|
try {
|
|
let insertCols = [
|
|
'group_id',
|
|
'project_id', 'create_date', 'uploader', 'bucket', 'file_path', 'ext',
|
|
'doc_direction', 'doc_number', 'doc_date',
|
|
'recipient_org', 'recipient_org_abbr', 'recipient_name', 'recipient_name_abbr',
|
|
'sender_org', 'sender_org_abbr', 'sender_name', 'sender_name_abbr',
|
|
'doc_title', 'doc_title_summary', 'doc_content_summary', 'doc_related_docs',
|
|
'doc_type', 'doc_category', 'attachment_title', 'attachment_count',
|
|
'doc_memo', 'doc_manager', 'doc_label',
|
|
'data_size', 'storage_type', 'object_key', 'preview_key', 'popup_key', 'is_aiused'
|
|
];
|
|
|
|
let values = [];
|
|
let placeholders = [];
|
|
|
|
for (let i = 0; i < dateArr.length; i++) {
|
|
let createDate = makePostgresTimestamp(dateArr[i]);
|
|
|
|
let resourcePath;
|
|
if (resourcePathArr) resourcePath = resourcePathArr[i];
|
|
resourcePath = resourcePath.startsWith('/') ? resourcePath.slice(1) : resourcePath;
|
|
|
|
let ext = (resourcePath.split('.').pop()).replace('.', '').toLowerCase();
|
|
|
|
let objectKey = null;
|
|
if (objectKeyArr) objectKey = objectKeyArr[i];
|
|
|
|
let previewKey = null, popupKey = null;
|
|
if (notNeedConvertExtArr.includes(ext)) {
|
|
previewKey = objectKey;
|
|
popupKey = objectKey;
|
|
}
|
|
|
|
let row = [];
|
|
|
|
row.push(
|
|
group_id,
|
|
projectId, makePostgresTimestamp(dateArr[i]), userId, bucket,
|
|
resourcePathArr[i], ext,
|
|
doc_direction, doc_number, doc_date,
|
|
recipient_org, recipient_org_abbr, recipient_name, recipient_name_abbr,
|
|
sender_org, sender_org_abbr, sender_name, sender_name_abbr,
|
|
doc_title, doc_title_summary, doc_content_summary, doc_related_docs,
|
|
doc_type, doc_category, attachment_title, attachment_count,
|
|
doc_memo, doc_manager, doc_label,
|
|
sizeArr[i], storageType, objectKeyArr[i], previewKey, popupKey, is_aiused
|
|
);
|
|
|
|
values.push(...row);
|
|
|
|
const base = i * row.length;
|
|
placeholders.push(`(${row.map((_, j) => `$${base + j + 1}`).join(', ')})`);
|
|
}
|
|
|
|
const queryString = `
|
|
INSERT INTO ver4.tb_official_doc_file (${insertCols.join(', ')})
|
|
VALUES ${placeholders.join(', ')}
|
|
RETURNING *;
|
|
`;
|
|
|
|
await client.query(queryString, values);
|
|
|
|
result = 'insertDocRows_success';
|
|
return result;
|
|
} catch (error) {
|
|
console.error("insertDocRows error:", error);
|
|
} finally {
|
|
client.release();
|
|
}
|
|
};
|
|
|
|
async function updateDocRows(params) {
|
|
let result;
|
|
|
|
let projectId = params.projectId;
|
|
let bucket = projectId;
|
|
let userInfo = params.userInfoString ? JSON.parse(params.userInfoString) : undefined;
|
|
let userId = (userInfo) ? userInfo.user_id : undefined;
|
|
let storageType = params.uploadParams?.storageType;
|
|
let activity = params.activity;
|
|
let dateNow = Date.now();
|
|
let modDate = makePostgresTimestamp(dateNow);
|
|
|
|
if (!userId) return;
|
|
|
|
const client = await pool.connect();
|
|
let {
|
|
doc_id,
|
|
doc_direction, doc_number, doc_date,
|
|
recipient_org, recipient_org_abbr, recipient_name, recipient_name_abbr,
|
|
sender_org, sender_org_abbr, sender_name, sender_name_abbr,
|
|
doc_title, doc_title_summary, doc_content_summary, doc_related_docs,
|
|
doc_type, doc_category,
|
|
attachment_title, attachment_count, doc_memo, doc_manager, doc_label,
|
|
mod_date, mod_user_id, mod_activity
|
|
} = params;
|
|
|
|
const values = [
|
|
doc_direction, doc_number, doc_date,
|
|
recipient_org, recipient_org_abbr, recipient_name, recipient_name_abbr,
|
|
sender_org, sender_org_abbr, sender_name, sender_name_abbr,
|
|
doc_title, doc_title_summary, doc_content_summary, doc_related_docs,
|
|
doc_type, doc_category,
|
|
attachment_title, attachment_count, doc_memo, doc_manager, doc_label,
|
|
modDate, mod_user_id, mod_activity,
|
|
doc_id
|
|
];
|
|
|
|
try {
|
|
let queryString = `
|
|
UPDATE ver4.${tbOfficialDocFile}
|
|
SET
|
|
doc_direction=$1,
|
|
doc_number=$2,
|
|
doc_date=$3,
|
|
recipient_org=$4,
|
|
recipient_org_abbr=$5,
|
|
recipient_name=$6,
|
|
recipient_name_abbr=$7,
|
|
sender_org=$8,
|
|
sender_org_abbr=$9,
|
|
sender_name=$10,
|
|
sender_name_abbr=$11,
|
|
doc_title=$12,
|
|
doc_title_summary=$13,
|
|
doc_content_summary=$14,
|
|
doc_related_docs=$15,
|
|
doc_type=$16,
|
|
doc_category=$17,
|
|
attachment_title=$18,
|
|
attachment_count=$19,
|
|
doc_memo=$20,
|
|
doc_manager=$21,
|
|
doc_label=$22,
|
|
mod_date=$23,
|
|
mod_user_id=$24,
|
|
mod_activity=$25
|
|
WHERE doc_id=$26
|
|
`;
|
|
|
|
await client.query(queryString, values);
|
|
result = 'updateDocRows_success';
|
|
|
|
return result;
|
|
} catch (error) {
|
|
console.error("updateDocRows error:", error);
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
async function updateExistRows(params) {
|
|
let result;
|
|
|
|
let projectId = params.projectId;
|
|
let bucket = projectId;
|
|
let userInfo = params.userInfoString ? JSON.parse(params.userInfoString) : undefined;
|
|
let userId = (userInfo) ? userInfo.user_id : undefined;
|
|
let storageType = params.uploadParams?.storageType;
|
|
let activity = params.activity;
|
|
let dateNow = Date.now();
|
|
let modDate = makePostgresTimestamp(dateNow);
|
|
|
|
if (!userId) return;
|
|
|
|
const client = await pool.connect();
|
|
let {
|
|
doc_id,
|
|
mod_date, mod_user_id, mod_activity,
|
|
object_key, preview_key, popup_key
|
|
} = params;
|
|
|
|
const values = [
|
|
modDate, userId, mod_activity,
|
|
object_key, preview_key, popup_key,
|
|
doc_id
|
|
];
|
|
|
|
try {
|
|
let queryString = `
|
|
UPDATE ver4.${tbOfficialDocFile}
|
|
SET
|
|
mod_date=$1,
|
|
mod_user_id=$2,
|
|
mod_activity=$3,
|
|
object_key=$4,
|
|
preview_key=$5,
|
|
popup_key=$6
|
|
WHERE doc_id=$7
|
|
`;
|
|
|
|
await client.query(queryString, values);
|
|
result = 'updateExistRows_success';
|
|
|
|
return result;
|
|
} catch (error) {
|
|
console.error("updateExistRows error:", error);
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
async function deleteDocRows(projectId, docId, groupId, isOfficialDoc) {
|
|
const client = await pool.connect();
|
|
|
|
try {
|
|
let queryString;
|
|
let values;
|
|
|
|
if (isOfficialDoc) {
|
|
// 공문이면 group_id로 첨부파일까지 전체 삭제
|
|
queryString = `
|
|
DELETE
|
|
FROM ver4.${tbOfficialDocFile}
|
|
WHERE project_id = $1 AND group_id = $2
|
|
`;
|
|
values = [projectId, groupId];
|
|
} else {
|
|
// 첨부파일이면 doc_id로 해당 파일만 삭제
|
|
queryString = `
|
|
DELETE
|
|
FROM ver4.${tbOfficialDocFile}
|
|
WHERE project_id = $1 AND doc_id = $2
|
|
`;
|
|
values = [projectId, docId];
|
|
}
|
|
|
|
await client.query(queryString, values);
|
|
return { success: true };
|
|
} catch (error) {
|
|
console.error('deleteDocRows error:', error);
|
|
return { success: false, message: 'DB 삭제 오류', error };
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
exports.convertDocPdf = async(req, res) => {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
let bucket = projectId;
|
|
let userIp = req.ip;
|
|
|
|
let { params } = req.body;
|
|
let { storageType, objectKey, resourcePath, docId, userInfoString } = params;
|
|
|
|
//// 배열에 현재 변환중인 파일 경로 추가
|
|
convertingDocPathArr.push(resourcePath);
|
|
|
|
//// 변환 시작 socket 전송
|
|
let resultData = { resourcePath: resourcePath, convertingDocPathArr: convertingDocPathArr };
|
|
let io = getIo();
|
|
io.emit('convertDoc_start', resultData);
|
|
|
|
let command = new GetObjectCommand({
|
|
Bucket: bucket,
|
|
Key: objectKey,
|
|
});
|
|
|
|
let url = await getSignedUrl(s3, command, { expiresIn: 60 * 5 }); // 5분 유효
|
|
|
|
// 대기 중인 작업 수 확인
|
|
let waitingCount = await convertPdfQueue.getWaitingCount();
|
|
|
|
// 작업 시작자(initiator) 결정
|
|
let initiator = `DEV_LOCAL_${JSON.parse(params.userInfoString).user_id}`;
|
|
if (env == 'production') {
|
|
if (deploymentType == 'ONPREMISE') initiator = 'HYHC_ONPREMISE';
|
|
if (deploymentType == 'CLOUD') initiator = `AWS_CLOUD_${cloudType}`;
|
|
}
|
|
|
|
let type = 'officialDoc';
|
|
let dataId = docId;
|
|
|
|
// add() 함수로 큐에 새로운 작업을 추가
|
|
// queue.add(name, data, options);
|
|
// name: 작업의 이름
|
|
// data: 작업에 대한 데이터, 작업이 처리될 때 필요한 데이터나 인자를 포함. 이 파라미터는 객체 형태로 전달됨
|
|
// option: 작업에 대한 옵션(선택적)
|
|
const job = await convertPdfQueue.add(
|
|
`'${initiator}'에서 문서를 PDF로 변환`,
|
|
{ resourcePath, url, objectKey, bucket, storageType, dataId, projectId, userInfoString, userIp, initiator, type },
|
|
// { jobId: `pdf-${Date.now()}` } // ← 원하는 ID 값 지정
|
|
);
|
|
|
|
res.status(200).json({
|
|
jobId: job.id,
|
|
waiting: waitingCount
|
|
});
|
|
|
|
// queue.js에서 변환 완료/실패 후 소켓으로 convertPdf_success/convertPdf_failed 이벤트 전송
|
|
}
|
|
|
|
async function getAttachmentObjectKeys(projectId, groupId) {
|
|
const client = await pool.connect();
|
|
|
|
try {
|
|
const queryString = `
|
|
SELECT object_key, popup_key
|
|
FROM ver4.${tbOfficialDocFile}
|
|
WHERE project_id = $1
|
|
AND group_id = $2
|
|
AND object_key LIKE '%__attachment/%'
|
|
`;
|
|
const result = await client.query(queryString, [projectId, groupId]);
|
|
return result.rows.map(row => ({
|
|
object_key: row.object_key,
|
|
popup_key: row.popup_key
|
|
}));
|
|
} catch (error) {
|
|
console.error('❌ getAttachmentObjectKeys error:', error);
|
|
return [];
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
exports.getExtractKey = async (req, res) => {
|
|
// const apiKey = 'sk-a518fc3f1955915440d402f95de982de'; // IP 기반 호출
|
|
const apiKey = 'sk-dc2c9312ac5538366bb61d4dee3d5832'; // 도메인 기반 호출
|
|
|
|
if (apiKey) {
|
|
res.status(200).json({
|
|
message: 'getExtractKey_success',
|
|
apiKey: apiKey,
|
|
});
|
|
} else {
|
|
res.status(400).json({
|
|
message: '🚫 API 키가 없습니다.',
|
|
});
|
|
}
|
|
}
|
|
|
|
exports.checkDocTargetExists = async (req, res) => {
|
|
const projectId = req.baseUrl.split('/')[1];
|
|
|
|
let { params } = req.body;
|
|
params.projectId = projectId;
|
|
|
|
let checkDocTargetExistsActionResult = await checkDocTargetExistsAction(params);
|
|
if (checkDocTargetExistsActionResult.message == 'checkDocTargetExistsAction_success') {
|
|
res.status(200).json({
|
|
message: 'checkDocTargetExists_success',
|
|
rows: checkDocTargetExistsActionResult.results
|
|
});
|
|
}
|
|
}
|
|
|
|
async function checkDocTargetExistsAction(params) {
|
|
let { projectId, storageType, docLabel, resourcePathArr } = params;
|
|
let bucket = projectId;
|
|
resourcePathArr = JSON.parse(resourcePathArr);
|
|
|
|
const client = await pool.connect();
|
|
try {
|
|
let results = [];
|
|
|
|
for (const path of resourcePathArr) {
|
|
const queryString = `
|
|
SELECT
|
|
doc_id,
|
|
project_id,
|
|
bucket,
|
|
file_path,
|
|
ext,
|
|
doc_label,
|
|
storage_type,
|
|
object_key,
|
|
preview_key,
|
|
popup_key,
|
|
group_id
|
|
FROM ver4.tb_official_doc_file
|
|
WHERE file_path = $1
|
|
AND project_id = $2
|
|
AND bucket = $3
|
|
AND storage_type = $4
|
|
AND doc_label = $5
|
|
`;
|
|
|
|
const { rows } = await client.query(queryString, [path, projectId, bucket, storageType, docLabel]);
|
|
|
|
if (rows.length != 0) results.push({ rows: rows });
|
|
}
|
|
|
|
return {
|
|
message: 'checkDocTargetExistsAction_success',
|
|
results
|
|
};
|
|
|
|
} catch (error) {
|
|
console.error("checkDocTargetExistsAction err: ", error);
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
|
|
exports.docGeminiAiAction = async(req, res) => {
|
|
if (!req.files) {
|
|
console.log('파일없음')
|
|
return res.status(400).send("파일이 없습니다.");
|
|
}
|
|
|
|
const inputFile = req.files['input_file'][0]; // input_file
|
|
const promptFile = req.files['prompt_file'][0]; // prompt_file
|
|
const schemaFile = req.files['schema_file'][0]; // schema_file
|
|
|
|
if (!inputFile || !promptFile || !schemaFile) return res.status(400).send('파일 없음');
|
|
|
|
// console.log(inputFile, promptFile, schemaFile);
|
|
|
|
try {
|
|
// 1. 파일 버퍼 가져오기 및 인코딩
|
|
const inputFileBuffer = inputFile.buffer;
|
|
const base64Pdf = inputFileBuffer.toString('base64');
|
|
// 이미지테스트
|
|
// const base64Image = inputFileBuffer.toString('base64');
|
|
const promptFileBuffer = promptFile.buffer;
|
|
const promptText = promptFileBuffer.toString('utf-8');
|
|
|
|
// 2. 스키마 json 파싱
|
|
const schemaBuffer = schemaFile.buffer;
|
|
const schemaText = schemaBuffer.toString('utf8');
|
|
const schema = JSON.parse(schemaText);
|
|
|
|
if (!inputFileBuffer || !promptFileBuffer) {
|
|
return res.status(400).send('파일을 처리하는데 오류가 발생했습니다.');
|
|
}
|
|
|
|
// 3. Gemini API 요청
|
|
const { GoogleGenerativeAI } = require('@google/generative-ai');
|
|
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
|
|
const model = genAI.getGenerativeModel({ model: 'gemini-2.5-flash' });
|
|
|
|
const result = await model.generateContent({
|
|
contents: [ // contents 배열로 감싸기
|
|
{
|
|
parts: [
|
|
{ text: promptText }, // 텍스트(프롬프트) 부분
|
|
{
|
|
inline_data: { // pdf(파일) 부분
|
|
mime_type: 'application/pdf',
|
|
data: base64Pdf,
|
|
// mime_type: 'image/jpeg', // 이미지테스트 //image/png
|
|
// data: base64Image, // 이미지테스트
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
generationConfig: {
|
|
responseMimeType: 'application/json',
|
|
responseSchema: schema,
|
|
},
|
|
});
|
|
|
|
const json = await result.response.text();
|
|
res.status(200).json({
|
|
data: json,
|
|
})
|
|
|
|
// 예상 토큰수 확인
|
|
const countResult = await model.countTokens({
|
|
contents: [
|
|
{
|
|
role: 'user',
|
|
parts: [
|
|
{ text: promptText },
|
|
{
|
|
inline_data: { // pdf(파일) 부분
|
|
mime_type: 'application/pdf',
|
|
data: base64Pdf,
|
|
// mime_type: 'image/jpeg', // 이미지테스트 //image/png
|
|
// data: base64Image, // 이미지테스트
|
|
},
|
|
}
|
|
]
|
|
}
|
|
]
|
|
});
|
|
console.log(">> 예상 사용 토큰 수:", countResult.totalTokens);
|
|
|
|
} catch(err) {
|
|
// console.error(err);
|
|
if(err.status == 429) {
|
|
console.error("🚫 사용량 초과! 잠시 후 다시 시도하세요.");
|
|
} else {
|
|
throw err;
|
|
}
|
|
}
|
|
} |