refactor: standardize modal system, unify hardware DB schemas, and implement automatic asset reclassification
This commit is contained in:
241
server.js
241
server.js
@@ -11,7 +11,6 @@ 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,
|
||||
@@ -23,14 +22,14 @@ const pool = mysql.createPool({
|
||||
queueLimit: 0
|
||||
});
|
||||
|
||||
// --- 공통 헬퍼 함수 ---
|
||||
async function batchSave(tableName, assets, mapFn) {
|
||||
// 공통 배치 저장 로직
|
||||
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 } = mapFn(assets);
|
||||
const { sql, values } = getQuery(assets);
|
||||
await connection.query(sql, [values]);
|
||||
}
|
||||
await connection.commit();
|
||||
@@ -43,111 +42,173 @@ async function batchSave(tableName, assets, mapFn) {
|
||||
}
|
||||
}
|
||||
|
||||
// --- 1. 개인PC (PC) API ---
|
||||
// 공통 하드웨어 매핑 함수
|
||||
const mapHardware = (r, defaultType) => ({
|
||||
id: r.id,
|
||||
법인: r.corp,
|
||||
자산코드: r.asset_code,
|
||||
구매일: r.purchase_date,
|
||||
purchase_date: r.purchase_date,
|
||||
type: r.type || defaultType,
|
||||
상세용도: r.detail_purpose,
|
||||
detail_purpose: r.detail_purpose,
|
||||
용도: r.purpose,
|
||||
purpose: r.purpose,
|
||||
상세: r.details,
|
||||
details: r.details,
|
||||
현사용조직: r.current_org,
|
||||
current_org: r.current_org,
|
||||
이전사용조직: r.prev_org,
|
||||
prev_org: r.prev_org,
|
||||
위치: r.location,
|
||||
location: r.location,
|
||||
담당자_정: r.manager_main,
|
||||
manager_main: r.manager_main,
|
||||
담당자_부: r.manager_sub,
|
||||
manager_sub: r.manager_sub,
|
||||
IP주소: r.ip_address,
|
||||
ip_address: r.ip_address,
|
||||
원격접속: r.remote_tool,
|
||||
remote_tool: r.remote_tool,
|
||||
서버ID: r.server_id,
|
||||
server_id: r.server_id,
|
||||
서버PW: r.server_pw,
|
||||
server_pw: r.server_pw,
|
||||
모델명: r.model_name,
|
||||
model_name: r.model_name,
|
||||
OS: r.os,
|
||||
os: r.os,
|
||||
CPU: r.cpu,
|
||||
cpu: r.cpu,
|
||||
RAM: r.ram,
|
||||
ram: r.ram,
|
||||
GPU: r.gpu,
|
||||
gpu: r.gpu,
|
||||
SSD1: r.storage1,
|
||||
storage1: r.storage1,
|
||||
SSD2: r.storage2,
|
||||
storage2: r.storage2,
|
||||
HDD1: r.storage3,
|
||||
storage3: r.storage3,
|
||||
모니터링: r.monitoring,
|
||||
monitoring: r.monitoring,
|
||||
금액: r.price,
|
||||
price: r.price,
|
||||
비고: r.remarks,
|
||||
remarks: r.remarks
|
||||
});
|
||||
|
||||
// 공통 하드웨어 저장 값 생성 함수
|
||||
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.비고||''
|
||||
];
|
||||
|
||||
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)
|
||||
VALUES ?
|
||||
`;
|
||||
|
||||
// --- 1. 개인PC API ---
|
||||
app.get('/api/pc', async (req, res) => {
|
||||
try {
|
||||
const [rows] = await pool.query('SELECT * FROM pc_assets');
|
||||
const mapped = rows.map(r => ({
|
||||
id: r.id, type: '개인PC', 법인: r.corp, 자산코드: r.asset_code, 사용자: r.user, 위치: r.location,
|
||||
CPU: r.cpu, GPU: r.gpu, RAM: r.ram, SSD1: r.ssd1, SSD2: r.ssd2, HDD1: r.hdd1, HDD2: r.hdd2,
|
||||
IP주소: r.ip_address, HW사양: r.hw_spec, 구매일: r.purchase_date, 금액: r.price,
|
||||
납품업체: r.vendor, 품의서명: r.doc_name, 비고: r.remarks
|
||||
}));
|
||||
res.json(mapped);
|
||||
res.json(rows.map(r => mapHardware(r, 'PC')));
|
||||
} catch (err) { 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: `INSERT INTO pc_assets (id, corp, asset_code, user, location, cpu, gpu, ram, ssd1, ssd2, hdd1, hdd2, ip_address, hw_spec, purchase_date, price, vendor, doc_name, remarks) VALUES ?`,
|
||||
values: assets.map(a => [a.id, a.법인||'', a.자산코드||'', a.사용자||'', a.위치||'', a.CPU||'', a.GPU||'', a.RAM||'', a.SSD1||'', a.SSD2||'', a.HDD1||'', a.HDD2||'', a.IP주소||'', a.HW사양||'', a.구매일||'', a.금액||'', a.납품업체||'', a.품의서명||'', a.비고||''])
|
||||
sql: hardwareInsertSQL('pc_assets'),
|
||||
values: assets.map(getHardwareValues)
|
||||
}));
|
||||
res.json(result);
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
|
||||
// --- 2. 서버 (Server) API ---
|
||||
// --- 2. 서버 API ---
|
||||
app.get('/api/server', async (req, res) => {
|
||||
try {
|
||||
const [rows] = await pool.query('SELECT * FROM server_assets');
|
||||
const mapped = rows.map(r => ({
|
||||
id: r.id, type: '서버', 법인: r.corp, 자산코드: r.asset_code, 구매일: r.purchase_date, storage유형: r.type,
|
||||
용도: 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.remarks
|
||||
}));
|
||||
res.json(mapped);
|
||||
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: `INSERT INTO server_assets (id, corp, asset_code, purchase_date, type, 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, remarks) VALUES ?`,
|
||||
values: assets.map(a => [a.id, a.법인||'', a.자산코드||'', a.구매일||'', a.storage유형||'물리', 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.비고||''])
|
||||
sql: hardwareInsertSQL('server_assets'),
|
||||
values: assets.map(getHardwareValues)
|
||||
}));
|
||||
res.json(result);
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
|
||||
// --- 3. 스토리지 (Storage) API ---
|
||||
// --- 3. 스토리지 API ---
|
||||
app.get('/api/storage', async (req, res) => {
|
||||
try {
|
||||
const [rows] = await pool.query('SELECT * FROM storage_assets');
|
||||
const mapped = rows.map(r => ({
|
||||
id: r.id, type: '스토리지', 법인: r.corp, storage유형: r.type, 자산코드: r.asset_code, 명칭: r.asset_name,
|
||||
위치: r.location, 모델명: r.model_name, 용량: r.capacity, 담당자_정: r.manager_main, 담당자_부: r.manager_sub,
|
||||
IP주소: r.ip_address, MACaddress: r.mac_address, 구매일: r.purchase_date, 금액: r.price,
|
||||
납품업체: r.vendor, 품의서명: r.doc_name, 비고: r.remarks
|
||||
}));
|
||||
res.json(mapped);
|
||||
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: `INSERT INTO storage_assets (id, corp, type, asset_code, asset_name, location, model_name, capacity, manager_main, manager_sub, ip_address, mac_address, purchase_date, price, vendor, doc_name, remarks) VALUES ?`,
|
||||
values: assets.map(a => [a.id, a.법인||'', a.storage유형||'', a.자산코드||'', a.명칭||'', a.위치||'', a.모델명||'', a.용량||'', a.담당자_정||'', a.담당자_부||'', a.IP주소||'', a.MACaddress||'', a.구매일||'', a.금액||'', a.납품업체||'', a.품의서명||'', a.비고||''])
|
||||
sql: hardwareInsertSQL('storage_assets'),
|
||||
values: assets.map(getHardwareValues)
|
||||
}));
|
||||
res.json(result);
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
|
||||
// --- 4. 전산비품 (Equipment) API ---
|
||||
// --- 4. 전산비품 API ---
|
||||
app.get('/api/equip', async (req, res) => {
|
||||
try {
|
||||
const [rows] = await pool.query('SELECT * FROM equip_assets');
|
||||
const mapped = rows.map(r => ({
|
||||
id: r.id, type: '전산비품', 법인: r.corp, 비품유형: r.type, 자산코드: r.asset_code, 명칭: r.asset_name,
|
||||
위치: r.location, 관리자: r.manager, IP주소: r.ip_address, MACaddress: r.mac_address,
|
||||
HW사양: r.hw_spec, OS: r.os, 구매일: r.purchase_date, 금액: r.price,
|
||||
납품업체: r.vendor, 품의서명: r.doc_name, 비고: r.remarks
|
||||
}));
|
||||
res.json(mapped);
|
||||
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: `INSERT INTO equip_assets (id, corp, type, asset_code, asset_name, location, manager, ip_address, mac_address, hw_spec, os, purchase_date, price, vendor, doc_name, remarks) VALUES ?`,
|
||||
values: assets.map(a => [a.id, a.법인||'', a.비품유형||'', a.자산코드||'', a.명칭||'', a.위치||'', a.관리자||'', a.IP주소||'', a.MACaddress||'', a.HW사양||'', a.OS||'', a.구매일||'', a.금액||'', a.납품업체||'', a.품의서명||'', a.비고||''])
|
||||
sql: hardwareInsertSQL('equip_assets'),
|
||||
values: assets.map(getHardwareValues)
|
||||
}));
|
||||
res.json(result);
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
|
||||
// --- 5. 구독SW (Subscription) API ---
|
||||
// --- 5. 모바일기기 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 }); }
|
||||
});
|
||||
|
||||
// --- 6. 소프트웨어 구독 API ---
|
||||
app.get('/api/sw/sub', async (req, res) => {
|
||||
try {
|
||||
const [rows] = await pool.query('SELECT * FROM subscription_sw');
|
||||
const [rows] = await pool.query('SELECT * FROM sw_sub_assets');
|
||||
const mapped = rows.map(r => ({
|
||||
id: r.id, type: '구독SW', 분야: r.category, 법인: r.corp, 부서: r.dept, 제품명: r.product_name,
|
||||
구매일: r.purchase_date, 구독일: r.subscription_date, 금액: r.price, 수량: r.quantity,
|
||||
계정명: r.account_id, 납품업체: r.vendor, 비고: r.remarks
|
||||
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
|
||||
}));
|
||||
res.json(mapped);
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
@@ -155,22 +216,22 @@ app.get('/api/sw/sub', async (req, res) => {
|
||||
|
||||
app.post('/api/sw/sub/batch', async (req, res) => {
|
||||
try {
|
||||
const result = await batchSave('subscription_sw', req.body, (assets) => ({
|
||||
sql: `INSERT INTO subscription_sw (id, category, corp, dept, product_name, purchase_date, subscription_date, price, quantity, account_id, vendor, remarks) VALUES ?`,
|
||||
values: assets.map(a => [a.id, a.분야||'', a.법인||'', a.부서||'', a.제품명||'', a.구매일||'', a.구독일||'', a.금액||'', a.수량||1, a.계정명||'', a.납품업체||'', a.비고||''])
|
||||
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 }); }
|
||||
});
|
||||
|
||||
// --- 6. 영구SW (Permanent) API ---
|
||||
// --- 7. 소프트웨어 영구 API ---
|
||||
app.get('/api/sw/perm', async (req, res) => {
|
||||
try {
|
||||
const [rows] = await pool.query('SELECT * FROM permanent_sw');
|
||||
const [rows] = await pool.query('SELECT * FROM sw_perm_assets');
|
||||
const mapped = rows.map(r => ({
|
||||
id: r.id, type: '영구SW', 분야: r.category, 법인: r.corp, 부서: r.dept, 제품명: r.product_name,
|
||||
구매일: r.purchase_date, 유지보수여부: !!r.maintenance_status, 금액: r.price, 수량: r.quantity,
|
||||
계정명: r.account_id, 납품업체: r.vendor, 비고: r.remarks
|
||||
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
|
||||
}));
|
||||
res.json(mapped);
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
@@ -178,35 +239,75 @@ app.get('/api/sw/perm', async (req, res) => {
|
||||
|
||||
app.post('/api/sw/perm/batch', async (req, res) => {
|
||||
try {
|
||||
const result = await batchSave('permanent_sw', req.body, (assets) => ({
|
||||
sql: `INSERT INTO permanent_sw (id, category, corp, dept, product_name, purchase_date, maintenance_status, price, quantity, account_id, vendor, remarks) VALUES ?`,
|
||||
values: assets.map(a => [a.id, a.분야||'', a.법인||'', a.부서||'', a.제품명||'', a.구매일||'', a.유지보수여부?1:0, a.금액||'', a.수량||1, a.계정명||'', a.납품업체||'', a.비고||''])
|
||||
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 }); }
|
||||
});
|
||||
|
||||
// --- 7. SW 사용자 (SW Users) API ---
|
||||
// --- 8. 소프트웨어 사용자 관리 API ---
|
||||
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
|
||||
const result = rows.map(u => ({
|
||||
sw_id: u.sw_id,
|
||||
userData: [u.corp||'', u.dept||'', u.position||'', u.user_name||'', u.usage_period||'', u.doc_name||'']
|
||||
}));
|
||||
res.json(mapped);
|
||||
res.json(result);
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
|
||||
app.post('/api/sw-users/batch', async (req, res) => {
|
||||
try {
|
||||
const result = await batchSave('sw_users', req.body, (users) => ({
|
||||
sql: `INSERT INTO sw_users (id, sw_id, corp, dept, team, position, name, usage_period, doc_name) VALUES ?`,
|
||||
values: users.map(u => [u.id, u.swId, u.법인||'', u.부서||'', u.팀||'', u.직위||'', u.이름||'', u.사용기간||'', u.신청서명||''])
|
||||
}));
|
||||
res.json(result);
|
||||
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.userDataList.map(u => [item.sw_id, u.구매법인||u.법인||'', u.부서||'', u.직위||'', u.이름||'', u.사용기간||'', u.신청서명||''])
|
||||
);
|
||||
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 = ['server_assets', 'pc_assets', 'storage_assets', 'equip_assets', 'mobile_assets'];
|
||||
let maxNum = 0;
|
||||
|
||||
for (const table of tables) {
|
||||
const [rows] = await pool.query(
|
||||
`SELECT asset_code as 자산코드 FROM ${table} WHERE asset_code LIKE ? ORDER BY asset_code DESC LIMIT 1`,
|
||||
[`${prefix}%`]
|
||||
);
|
||||
|
||||
if (rows.length > 0) {
|
||||
const lastCode = rows[0].자산코드;
|
||||
const lastNum = parseInt(lastCode.split('-').pop() || '0');
|
||||
if (lastNum > maxNum) maxNum = lastNum;
|
||||
}
|
||||
}
|
||||
|
||||
const nextNum = String(maxNum + 1).padStart(3, '0');
|
||||
res.json({ nextCode: `${prefix}${nextNum}` });
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`📡 ITAM Dedicated API Server running on http://localhost:${PORT}`);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user