const pool = require("../db/pool.js"); const { DeleteObjectCommand } = require("@aws-sdk/client-s3"); const onPremiseClient = require('../config/onPremiseClient.js'); const cloudClient = require('../config/cloudClient.js'); const storageClients = { 'ONPREMISE': onPremiseClient, 'CLOUD': cloudClient }; const s3 = storageClients[process.env.DEPLOYMENT_TYPE || 'ONPREMISE']; const env = process.env.NODE_ENV; const tbData = env === 'production' ? 'tb_data' : '_test_tb_data'; async function runAutoClean() { const client = await pool.connect(); try { console.log("⏰ Running auto clean batch job..."); // 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) { console.log("⏰ Auto clean policy is disabled or not found."); return; } const { limit_file_count, limit_days } = policyRes.rows[0]; // 2. 삭제 대상 파일 검색 (각 현장의 파일 개수가 limit_file_count 미만이고, limit_days 일이 지난 파일) const targetQuery = ` SELECT data_id, project_id, object_key, preview_key, popup_key, thumbnail_key, path1, path2, path3, path4, path5, path6, path7, path8, data_depth, is_folder FROM ver4.${tbData} WHERE is_folder = false AND is_removed = false AND project_id IN ( SELECT project_id FROM ver4.${tbData} WHERE is_folder = false AND is_removed = false GROUP BY project_id HAVING COUNT(*) < $1 ) AND create_date < NOW() - CAST($2 || ' days' AS INTERVAL); `; const targets = await client.query(targetQuery, [limit_file_count, limit_days]); console.log(`⏰ Found ${targets.rows.length} files to clean up.`); for (const file of targets.rows) { let success = true; let keysToDelete = []; if (file.object_key) keysToDelete.push(file.object_key); if (file.preview_key) keysToDelete.push(file.preview_key); if (file.popup_key) keysToDelete.push(file.popup_key); if (file.thumbnail_key) keysToDelete.push(file.thumbnail_key); // S3 실물 파일 삭제 for (const key of keysToDelete) { try { const command = new DeleteObjectCommand({ Bucket: file.project_id, // 현장 ID를 버킷 명으로 사용함 Key: key }); await s3.send(command); } catch (s3Err) { console.error(`❌ S3 Delete Error [Key: ${key}]:`, s3Err.message); success = false; } } // DB 메타 정보 완전 제거 try { await client.query(`DELETE FROM ver4.${tbData} WHERE data_id = $1`, [file.data_id]); } catch (dbErr) { console.error(`❌ DB Delete Error [DataID: ${file.data_id}]:`, dbErr.message); success = false; } // 삭제 경로 조합 let cleanPath = ''; for (let i = 1; i <= file.data_depth; i++) { if (file[`path${i}`]) cleanPath += '/' + file[`path${i}`]; } // 로그 기록 const criteria = `보관수량 ${limit_file_count}개 미만 / 기한 ${limit_days}일 경과`; await client.query(` INSERT INTO ver4.tb_auto_clean_log (clean_date, project_id, clean_path, criteria_info, result_status) VALUES (CURRENT_TIMESTAMP, 'SYSTEM', $1, $2, $3); `, [cleanPath, criteria, success ? 'SUCCESS' : 'FAILED']); } console.log("⏰ Auto clean batch job finished successfully."); } catch (err) { console.error("❌ Auto Clean Batch Error:", err); } finally { client.release(); } } // 매일 자정에 한 번 가동하는 타이머 등록 함수 function startScheduler() { const now = new Date(); const target = new Date(); target.setHours(0, 0, 0, 0); if (now > target) { target.setDate(target.getDate() + 1); } const delay = target - now; console.log(`⏰ Scheduler loaded. Next run in ${Math.floor(delay / 1000 / 60)} minutes.`); setTimeout(async () => { await runAutoClean(); // 실행 후 다음날 자정으로 다시 예약 startScheduler(); }, delay); } module.exports = { start: startScheduler, runNow: runAutoClean };