import express from 'express'; import mysql from 'mysql2/promise'; import cors from 'cors'; import dotenv from 'dotenv'; dotenv.config(); const app = express(); const PORT = process.env.PORT || 3000; app.use(cors()); app.use(express.json({ limit: '50mb' })); const pool = mysql.createPool({ host: process.env.DB_HOST, user: process.env.DB_USER, password: process.env.DB_PASS, database: process.env.DB_NAME, port: parseInt(process.env.DB_PORT || '3306'), waitForConnections: true, connectionLimit: 10, queueLimit: 0 }); // 테이블 존재 여부 확인 및 자동 생성 async function ensureTables() { const connection = await pool.getConnection(); try { await connection.query(` CREATE TABLE IF NOT EXISTS cloud_assets ( id VARCHAR(50) PRIMARY KEY, platform_name VARCHAR(100), corp VARCHAR(100), dept VARCHAR(100), product_name VARCHAR(255), account_name VARCHAR(255), pay_method VARCHAR(100), pay_day VARCHAR(50), card_num VARCHAR(100), monthly_fee VARCHAR(100), remarks TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; `); await connection.query(` CREATE TABLE IF NOT EXISTS asset_logs ( id VARCHAR(50) PRIMARY KEY, asset_id VARCHAR(50), log_date VARCHAR(50), log_user VARCHAR(100), details TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; `); console.log('✅ Cloud & Logs tables ensured.'); } finally { connection.release(); } } // 공통 배치 저장 로직 async function batchSave(tableName, assets, getQuery) { const connection = await pool.getConnection(); try { await connection.beginTransaction(); await connection.query(`DELETE FROM ${tableName}`); if (assets.length > 0) { const { sql, values } = getQuery(assets); await connection.query(sql, [values]); } await connection.commit(); return { success: true, count: assets.length }; } catch (err) { await connection.rollback(); throw err; } finally { connection.release(); } } // 하드웨어 쿼리 헬퍼 const hardwareInsertSQL = (table) => ` INSERT INTO ${table} ( id, corp, asset_code, purchase_date, type, detail_purpose, purpose, details, current_org, prev_org, location, manager_main, manager_sub, ip_address, remote_tool, server_id, server_pw, model_name, os, cpu, ram, gpu, storage1, storage2, storage3, monitoring, price, remarks, storage_location, status ) VALUES ? `; const getHardwareValues = (a) => [ a.id, a.법인||'', a.자산코드||'', a.구매일||'', a.type||'', a.상세용도||'', a.용도||'', a.상세||'', a.현사용조직||'', a.이전사용조직||'', a.위치||'', a.담당자_정||'', a.담당자_부||'', a.IP주소||'', a.원격접속||'', a.서버ID||'', a.서버PW||'', a.모델명||'', a.OS||'', a.CPU||'', a.RAM||'', a.GPU||'', a.SSD1||'', a.SSD2||'', a.HDD1||'', a.모니터링||'', a.금액||'', a.비고||'', a.보관위치||'', a.현재상태||'' ]; const mapHardware = (r, defaultType) => ({ id: r.id, 법인: r.corp, 자산코드: r.asset_code, 구매일: r.purchase_date, type: r.type || defaultType, 상세용도: r.detail_purpose, 용도: r.purpose, 상세: r.details, 현사용조직: r.current_org, 이전사용조직: r.prev_org, 위치: r.location, 담당자_정: r.manager_main, 담당자_부: r.manager_sub, IP주소: r.ip_address, 원격접속: r.remote_tool, 서버ID: r.server_id, 서버PW: r.server_pw, 모델명: r.model_name, OS: r.os, CPU: r.cpu, RAM: r.ram, GPU: r.gpu, SSD1: r.storage1, SSD2: r.storage2, HDD1: r.storage3, 모니터링: r.monitoring, 금액: r.price, 비고: r.remarks, 보관위치: r.storage_location, 현재상태: r.status }); // --- API 라우트 정의 --- // PC API app.get('/api/pc', async (req, res) => { try { const [rows] = await pool.query('SELECT * FROM pc_assets'); console.log('🔍 DB Raw Rows (PC):', rows.length, 'items found.'); if (rows.length > 0) console.log('🔍 First row sample:', rows[0]); res.json(rows.map(r => mapHardware(r, '개인PC'))); } catch (err) { console.error('❌ DB Query Error (PC):', err.message); res.status(500).json({ error: err.message }); } }); app.post('/api/pc/batch', async (req, res) => { try { const result = await batchSave('pc_assets', req.body, (assets) => ({ sql: hardwareInsertSQL('pc_assets'), values: assets.map(getHardwareValues) })); res.json(result); } catch (err) { res.status(500).json({ error: err.message }); } }); // 서버 API app.get('/api/server', async (req, res) => { try { const [rows] = await pool.query('SELECT * FROM server_assets'); res.json(rows.map(r => mapHardware(r, '서버'))); } catch (err) { res.status(500).json({ error: err.message }); } }); app.post('/api/server/batch', async (req, res) => { try { const result = await batchSave('server_assets', req.body, (assets) => ({ sql: hardwareInsertSQL('server_assets'), values: assets.map(getHardwareValues) })); res.json(result); } catch (err) { res.status(500).json({ error: err.message }); } }); // 스토리지 API app.get('/api/storage', async (req, res) => { try { const [rows] = await pool.query('SELECT * FROM storage_assets'); res.json(rows.map(r => mapHardware(r, '스토리지'))); } catch (err) { res.status(500).json({ error: err.message }); } }); app.post('/api/storage/batch', async (req, res) => { try { const result = await batchSave('storage_assets', req.body, (assets) => ({ sql: hardwareInsertSQL('storage_assets'), values: assets.map(getHardwareValues) })); res.json(result); } catch (err) { res.status(500).json({ error: err.message }); } }); // 전산비품 API app.get('/api/equip', async (req, res) => { try { const [rows] = await pool.query('SELECT * FROM equip_assets'); res.json(rows.map(r => mapHardware(r, '전산비품'))); } catch (err) { res.status(500).json({ error: err.message }); } }); app.post('/api/equip/batch', async (req, res) => { try { const result = await batchSave('equip_assets', req.body, (assets) => ({ sql: hardwareInsertSQL('equip_assets'), values: assets.map(getHardwareValues) })); res.json(result); } catch (err) { res.status(500).json({ error: err.message }); } }); // 모바일 API app.get('/api/mobile', async (req, res) => { try { const [rows] = await pool.query('SELECT * FROM mobile_assets'); res.json(rows.map(r => mapHardware(r, '모바일기기'))); } catch (err) { res.status(500).json({ error: err.message }); } }); app.post('/api/mobile/batch', async (req, res) => { try { const result = await batchSave('mobile_assets', req.body, (assets) => ({ sql: hardwareInsertSQL('mobile_assets'), values: assets.map(getHardwareValues) })); res.json(result); } catch (err) { res.status(500).json({ error: err.message }); } }); // 구독 SW API app.get('/api/sw/sub', async (req, res) => { try { const [rows] = await pool.query('SELECT * FROM sw_sub_assets'); res.json(rows.map(r => ({ id: r.id, type: '구독SW', 법인: r.corp, 자산번호: r.asset_code, 제품명: r.product_name, 라이선스유형: r.license_type, 수량: r.quantity, 금액: r.price, 구매일: r.purchase_date, 만료일: r.expiry_date, 납품업체: r.vendor, 비고: r.remarks }))); } catch (err) { res.status(500).json({ error: err.message }); } }); app.post('/api/sw/sub/batch', async (req, res) => { try { const result = await batchSave('sw_sub_assets', req.body, (assets) => ({ sql: `INSERT INTO sw_sub_assets (id, corp, asset_code, product_name, license_type, quantity, price, purchase_date, expiry_date, vendor, remarks) VALUES ?`, values: assets.map(a => [a.id, a.법인||'', a.자산번호||'', a.제품명||'', a.라이선스유형||'', a.수량||0, a.금액||'', a.구매일||'', a.만료일||'', a.납품업체||'', a.비고||'']) })); res.json(result); } catch (err) { res.status(500).json({ error: err.message }); } }); // 영구 SW API app.get('/api/sw/perm', async (req, res) => { try { const [rows] = await pool.query('SELECT * FROM sw_perm_assets'); res.json(rows.map(r => ({ id: r.id, type: '영구SW', 법인: r.corp, 자산번호: r.asset_code, 제품명: r.product_name, 라이선스키: r.license_key, 수량: r.quantity, 금액: r.price, 구매일: r.purchase_date, 납품업체: r.vendor, 비고: r.remarks }))); } catch (err) { res.status(500).json({ error: err.message }); } }); app.post('/api/sw/perm/batch', async (req, res) => { try { const result = await batchSave('sw_perm_assets', req.body, (assets) => ({ sql: `INSERT INTO sw_perm_assets (id, corp, asset_code, product_name, license_key, quantity, price, purchase_date, vendor, remarks) VALUES ?`, values: assets.map(a => [a.id, a.법인||'', a.자산번호||'', a.제품명||'', a.라이선스키||'', a.수량||0, a.금액||'', a.구매일||'', a.납품업체||'', a.비고||'']) })); res.json(result); } catch (err) { res.status(500).json({ error: err.message }); } }); // 클라우드 API app.get('/api/cloud', async (req, res) => { try { const [rows] = await pool.query('SELECT * FROM cloud_assets'); res.json(rows.map(r => ({ id: r.id, type: '클라우드', 플랫폼명: r.platform_name, 법인: r.corp, 부서: r.dept, 제품명: r.product_name, 계정명: r.account_name, 결제수단: r.pay_method, 결제일: r.pay_day, 연결카드번호: r.card_num, 당월청구액: r.monthly_fee, 비고: r.remarks }))); } catch (err) { res.status(500).json({ error: err.message }); } }); app.post('/api/cloud/batch', async (req, res) => { try { const result = await batchSave('cloud_assets', req.body, (assets) => ({ sql: `INSERT INTO cloud_assets (id, platform_name, corp, dept, product_name, account_name, pay_method, pay_day, card_num, monthly_fee, remarks) VALUES ?`, values: assets.map(a => [a.id, a.플랫폼명||'', a.법인||'', a.부서||'', a.제품명||'', a.계정명||'', a.결제수단||'', a.결제일||'', a.연결카드번호||'', a.당월청구액||'', a.비고||'']) })); res.json(result); } catch (err) { res.status(500).json({ error: err.message }); } }); // 로그 API app.get('/api/logs', async (req, res) => { try { const [rows] = await pool.query('SELECT * FROM asset_logs ORDER BY log_date DESC'); res.json(rows.map(r => ({ id: r.id, assetId: r.asset_id, date: r.log_date, user: r.log_user, details: r.details }))); } catch (err) { res.status(500).json({ error: err.message }); } }); app.post('/api/logs/batch', async (req, res) => { try { const result = await batchSave('asset_logs', req.body, (assets) => ({ sql: `INSERT INTO asset_logs (id, asset_id, log_date, log_user, details) VALUES ?`, values: assets.map(a => [a.id, a.assetId||'', a.date||'', a.user||'', a.details||'']) })); res.json(result); } catch (err) { res.status(500).json({ error: err.message }); } }); // SW 사용자 API app.get('/api/sw-users', async (req, res) => { try { const [rows] = await pool.query('SELECT * FROM sw_users'); const grouped = rows.reduce((acc, u) => { if (!acc[u.sw_id]) acc[u.sw_id] = []; acc[u.sw_id].push([u.corp, u.dept, u.position, u.user_name, u.usage_period, u.doc_name]); return acc; }, {}); res.json(Object.keys(grouped).map(sw_id => ({ sw_id, userData: grouped[sw_id] }))); } catch (err) { res.status(500).json({ error: err.message }); } }); app.post('/api/sw-users/batch', async (req, res) => { try { const connection = await pool.getConnection(); await connection.beginTransaction(); await connection.query('DELETE FROM sw_users'); const allUsers = req.body; if (allUsers.length > 0) { const values = allUsers.flatMap(item => (item.userData || []).map(u => [item.sw_id, u[0], u[1], u[2], u[3], u[4], u[5]]) ); if (values.length > 0) { await connection.query('INSERT INTO sw_users (sw_id, corp, dept, position, user_name, usage_period, doc_name) VALUES ?', [values]); } } await connection.commit(); connection.release(); res.json({ success: true }); } catch (err) { res.status(500).json({ error: err.message }); } }); // 자산번호 자동 생성 API app.get('/api/generate-asset-code', async (req, res) => { const { prefix } = req.query; if (!prefix) return res.status(400).json({ error: 'Prefix is required' }); try { const tables = ['pc_assets', 'server_assets', 'storage_assets', 'equip_assets', 'mobile_assets']; let maxNum = 0; for (const table of tables) { const [rows] = await pool.query( `SELECT asset_code FROM ${table} WHERE asset_code LIKE ?`, [`${prefix}%`] ); rows.forEach(r => { const numPart = r.asset_code.replace(prefix, ''); const num = parseInt(numPart); if (!isNaN(num) && num > maxNum) maxNum = num; }); } const nextNum = (maxNum + 1).toString().padStart(3, '0'); res.json({ nextCode: `${prefix}${nextNum}` }); } catch (err) { res.status(500).json({ error: err.message }); } }); // 초기화 및 서버 기동 ensureTables().then(() => { app.listen(PORT, () => { console.log(`📡 ITAM Dedicated API Server running on http://localhost:${PORT}`); }); }).catch(err => { console.error('❌ Failed to start server:', err); });