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' })); // DB 연결 풀 생성 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 }); // --- API Routes --- // 1. 하드웨어 자산 조회 app.get('/api/hw', async (req, res) => { try { const [rows] = await pool.query('SELECT * FROM hw_assets'); // DB 컬럼명을 프론트엔드 인터페이스(한글)에 맞게 매핑 const mapped = rows.map(r => ({ id: r.id, type: r.type, 법인: r.corp, 자산코드: r.asset_code, 명칭: r.asset_name, 위치: r.location, 현사용조직: r.current_org, 이전사용조직: r.prev_org, 담당자_정: r.manager_main, 관리자: r.manager_main, 담당자_부: r.manager_sub, IP주소: r.ip_address, IP2: r.ip_address2, MACaddress: r.mac_address, OS: r.os, CPU: r.cpu, RAM: r.ram, SSD1: r.storage1, SSD2: r.storage2, 모델명: r.model_name, 구매일: r.purchase_date, 금액: r.price, 납품업체: r.vendor, 품의서명: r.doc_name, 용도: r.asset_name, // 서버의 경우 명칭을 용도로 사용 상세: r.remarks, 원격접속: r.remote_tool, 서버ID: r.server_id, 서버PW: r.server_pw, 모니터링: r.monitoring, 비고: r.remarks })); res.json(mapped); } catch (err) { res.status(500).json({ error: err.message }); } }); // 2. 하드웨어 자산 일괄 저장 (항상 덮어쓰기) app.post('/api/hw/batch', async (req, res) => { const assets = req.body; const connection = await pool.getConnection(); try { await connection.beginTransaction(); await connection.query('DELETE FROM hw_assets'); if (assets.length > 0) { const sql = ` INSERT INTO hw_assets ( id, type, corp, asset_code, asset_name, location, current_org, prev_org, manager_main, manager_sub, ip_address, ip_address2, mac_address, os, cpu, ram, storage1, storage2, model_name, purchase_date, price, vendor, doc_name, remote_tool, server_id, server_pw, monitoring, remarks ) VALUES ? `; const values = assets.map(a => [ a.id, a.type, a.법인, a.자산코드, a.명칭 || a.용도, a.위치, a.현사용조직, a.이전사용조직, a.담당자_정 || a.관리자, a.담당자_부, a.IP주소, a.IP2, a.MACaddress, a.OS, a.CPU, a.RAM, a.SSD1, a.SSD2, a.모델명, a.구매일, a.금액, a.납품업체, a.품의서명, a.원격접속, a.서버ID, a.서버PW, a.모니터링, a.비고 || a.상세 ]); await connection.query(sql, [values]); } await connection.commit(); res.json({ success: true, count: assets.length, mode: 'overwrite' }); } catch (err) { await connection.rollback(); res.status(500).json({ error: err.message }); } finally { connection.release(); } }); // 3. 소프트웨어 자산 조회 app.get('/api/sw', async (req, res) => { try { const [rows] = await pool.query('SELECT * FROM sw_assets'); const mapped = rows.map(r => ({ id: r.id, type: r.type, 분야: r.category, 법인: r.corp, 부서: r.dept, 제품명: r.product_name, 구매일: r.purchase_date, 구독일: r.subscription_date, 유지보수여부: !!r.maintenance_status, 금액: r.price, 수량: r.quantity, 계정명: r.account_id, 납품업체: r.vendor, 비고: r.remarks })); res.json(mapped); } catch (err) { res.status(500).json({ error: err.message }); } }); // 4. 소프트웨어 자산 일괄 저장 (항상 덮어쓰기) app.post('/api/sw/batch', async (req, res) => { const assets = req.body; const connection = await pool.getConnection(); try { await connection.beginTransaction(); await connection.query('DELETE FROM sw_assets'); if (assets.length > 0) { const sql = ` INSERT INTO sw_assets ( id, type, category, corp, dept, product_name, purchase_date, subscription_date, maintenance_status, price, quantity, account_id, vendor, remarks ) VALUES ? `; const values = assets.map(a => [ a.id, a.type, a.분야, a.법인, a.부서, a.제품명, a.구매일, a.구독일, a.유지보수여부 ? 1 : 0, a.금액, a.수량, a.계정명, a.납품업체, a.비고 ]); await connection.query(sql, [values]); } await connection.commit(); res.json({ success: true, count: assets.length, mode: 'overwrite' }); } catch (err) { await connection.rollback(); res.status(500).json({ error: err.message }); } finally { connection.release(); } }); // 5. SW 사용자 매핑 조회 app.get('/api/sw-users', async (req, res) => { try { const [rows] = await pool.query('SELECT * FROM sw_users'); const mapped = rows.map(r => ({ id: r.id, swId: r.sw_id, 법인: r.corp, 부서: r.dept, 팀: r.team, 직위: r.position, 이름: r.name, 사용기간: r.usage_period, 신청서명: r.doc_name })); res.json(mapped); } catch (err) { res.status(500).json({ error: err.message }); } }); // 6. SW 사용자 일괄 저장 (항상 덮어쓰기) app.post('/api/sw-users/batch', async (req, res) => { const users = req.body; const connection = await pool.getConnection(); try { await connection.beginTransaction(); await connection.query('DELETE FROM sw_users'); if (users.length > 0) { const sql = ` INSERT INTO sw_users ( id, sw_id, corp, dept, team, position, name, usage_period, doc_name ) VALUES ? `; const values = users.map(u => [ u.id, u.swId, u.법인, u.부서, u.팀, u.직위, u.이름, u.사용기간, u.신청서명 ]); await connection.query(sql, [values]); } await connection.commit(); res.json({ success: true, count: users.length, mode: 'overwrite' }); } catch (err) { await connection.rollback(); res.status(500).json({ error: err.message }); } finally { connection.release(); } }); app.listen(PORT, () => { console.log(`📡 ITAM API Server running on http://localhost:${PORT}`); });