refactor(backend): 일일 이슈 보고 API 전체 리팩토링
- dailyIssueReport 기능을 Controller-Service-Model 아키텍처로 재구성 - Model 계층을 Promise 기반으로 전환하고 트랜잭션을 적용하여 안정성 확보 - API 전반의 코드 품질 및 유지보수성 향상
This commit is contained in:
@@ -1,110 +1,58 @@
|
||||
const dailyIssueReportModel = require('../models/dailyIssueReportModel');
|
||||
// /controllers/dailyIssueReportController.js
|
||||
const dailyIssueReportService = require('../services/dailyIssueReportService');
|
||||
|
||||
// 1. CREATE: 단일 또는 다중 등록 (worker_id 배열 지원)
|
||||
exports.createDailyIssueReport = async (req, res) => {
|
||||
/**
|
||||
* 1. CREATE: 일일 이슈 보고서 생성 (Service Layer 사용)
|
||||
*/
|
||||
const createDailyIssueReport = async (req, res) => {
|
||||
try {
|
||||
const body = req.body;
|
||||
|
||||
// 기본 필드
|
||||
const base = {
|
||||
date: body.date,
|
||||
project_id: body.project_id,
|
||||
start_time: body.start_time,
|
||||
end_time: body.end_time,
|
||||
issue_type_id: body.issue_type_id
|
||||
};
|
||||
|
||||
if (!base.date || !base.project_id || !base.start_time || !base.end_time || !base.issue_type_id || !body.worker_id) {
|
||||
return res.status(400).json({ error: '필수 필드 누락' });
|
||||
}
|
||||
|
||||
// worker_id 배열화
|
||||
const workers = Array.isArray(body.worker_id) ? body.worker_id : [body.worker_id];
|
||||
const insertedIds = [];
|
||||
|
||||
for (const wid of workers) {
|
||||
const payload = { ...base, worker_id: wid };
|
||||
|
||||
const insertId = await new Promise((resolve, reject) => {
|
||||
dailyIssueReportModel.create(payload, (err, id) => {
|
||||
if (err) reject(err);
|
||||
else resolve(id);
|
||||
});
|
||||
});
|
||||
|
||||
insertedIds.push(insertId);
|
||||
}
|
||||
|
||||
res.json({ success: true, issue_report_ids: insertedIds });
|
||||
// 프론트엔드에서 worker_ids로 보내주기로 약속함
|
||||
const issueData = { ...req.body, worker_ids: req.body.worker_ids || req.body.worker_id };
|
||||
|
||||
const result = await dailyIssueReportService.createDailyIssueReportService(issueData);
|
||||
|
||||
res.status(201).json({ success: true, ...result });
|
||||
} catch (err) {
|
||||
console.error('🔥 createDailyIssueReport error:', err);
|
||||
res.status(500).json({ error: err.message || String(err) });
|
||||
console.error('💥 이슈 보고서 생성 컨트롤러 오류:', err);
|
||||
res.status(400).json({ success: false, error: err.message });
|
||||
}
|
||||
};
|
||||
|
||||
// 2. READ BY DATE
|
||||
exports.getDailyIssuesByDate = async (req, res) => {
|
||||
/**
|
||||
* 2. READ BY DATE: 날짜별 이슈 조회 (Service Layer 사용)
|
||||
*/
|
||||
const getDailyIssuesByDate = async (req, res) => {
|
||||
try {
|
||||
const { date } = req.query;
|
||||
const rows = await new Promise((resolve, reject) => {
|
||||
dailyIssueReportModel.getAllByDate(date, (err, data) => {
|
||||
if (err) reject(err);
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
res.json(rows);
|
||||
const issues = await dailyIssueReportService.getDailyIssuesByDateService(date);
|
||||
res.json(issues);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message || String(err) });
|
||||
console.error('💥 이슈 보고서 조회 컨트롤러 오류:', err);
|
||||
res.status(500).json({ success: false, error: err.message });
|
||||
}
|
||||
};
|
||||
|
||||
// 3. READ ONE
|
||||
exports.getDailyIssueById = async (req, res) => {
|
||||
/**
|
||||
* 3. DELETE: 이슈 보고서 삭제 (Service Layer 사용)
|
||||
*/
|
||||
const removeDailyIssue = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const row = await new Promise((resolve, reject) => {
|
||||
dailyIssueReportModel.getById(id, (err, data) => {
|
||||
if (err) reject(err);
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
if (!row) return res.status(404).json({ error: 'DailyIssueReport not found' });
|
||||
res.json(row);
|
||||
const result = await dailyIssueReportService.removeDailyIssueService(id);
|
||||
res.json({ success: true, ...result });
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message || String(err) });
|
||||
console.error('💥 이슈 보고서 삭제 컨트롤러 오류:', err);
|
||||
const statusCode = err.statusCode || 500;
|
||||
res.status(statusCode).json({ success: false, error: err.message });
|
||||
}
|
||||
};
|
||||
|
||||
// 4. UPDATE
|
||||
exports.updateDailyIssue = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const changes = await new Promise((resolve, reject) => {
|
||||
dailyIssueReportModel.update(id, req.body, (err, affectedRows) => {
|
||||
if (err) reject(err);
|
||||
else resolve(affectedRows);
|
||||
});
|
||||
});
|
||||
if (changes === 0) return res.status(404).json({ error: 'No changes or not found' });
|
||||
res.json({ success: true, changes });
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message || String(err) });
|
||||
}
|
||||
};
|
||||
// 레거시 함수들은 더 이상 라우팅되지 않으므로 제거하거나 주석 처리 가능
|
||||
// exports.getDailyIssueById = ...
|
||||
// exports.updateDailyIssue = ...
|
||||
|
||||
// 5. DELETE
|
||||
exports.removeDailyIssue = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const changes = await new Promise((resolve, reject) => {
|
||||
dailyIssueReportModel.remove(id, (err, affectedRows) => {
|
||||
if (err) reject(err);
|
||||
else resolve(affectedRows);
|
||||
});
|
||||
});
|
||||
if (changes === 0) return res.status(404).json({ error: 'DailyIssueReport not found' });
|
||||
res.json({ success: true, changes });
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message || String(err) });
|
||||
}
|
||||
module.exports = {
|
||||
createDailyIssueReport,
|
||||
getDailyIssuesByDate,
|
||||
removeDailyIssue,
|
||||
};
|
||||
@@ -1,51 +1,50 @@
|
||||
const { getDb } = require('../dbPool');
|
||||
|
||||
/**
|
||||
* 1. 등록 (단일 레코드)
|
||||
* 1. 여러 개의 이슈 보고서를 트랜잭션으로 생성합니다.
|
||||
* @param {Array<object>} reports - 생성할 보고서 데이터 배열
|
||||
* @returns {Promise<Array<number>>} - 삽입된 ID 배열
|
||||
*/
|
||||
const create = async (report, callback) => {
|
||||
const createMany = async (reports) => {
|
||||
const db = await getDb();
|
||||
const conn = await db.getConnection();
|
||||
try {
|
||||
const db = await getDb();
|
||||
const {
|
||||
date,
|
||||
worker_id,
|
||||
project_id,
|
||||
start_time,
|
||||
end_time,
|
||||
issue_type_id,
|
||||
description = null // 선택값 처리
|
||||
} = report;
|
||||
await conn.beginTransaction();
|
||||
|
||||
const [result] = await db.query(
|
||||
`INSERT INTO DailyIssueReports
|
||||
(date, worker_id, project_id, start_time, end_time, issue_type_id, description)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
[date, worker_id, project_id, start_time, end_time, issue_type_id, description]
|
||||
);
|
||||
const insertedIds = [];
|
||||
const sql = `
|
||||
INSERT INTO DailyIssueReports
|
||||
(date, worker_id, project_id, start_time, end_time, issue_type_id)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
`;
|
||||
|
||||
callback(null, result.insertId);
|
||||
for (const report of reports) {
|
||||
const { date, worker_id, project_id, start_time, end_time, issue_type_id } = report;
|
||||
const [result] = await conn.query(sql, [date, worker_id, project_id, start_time, end_time, issue_type_id]);
|
||||
insertedIds.push(result.insertId);
|
||||
}
|
||||
|
||||
await conn.commit();
|
||||
return insertedIds;
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
await conn.rollback();
|
||||
console.error('[Model] 여러 이슈 보고서 생성 중 오류:', err);
|
||||
throw new Error('데이터베이스에 이슈 보고서를 생성하는 중 오류가 발생했습니다.');
|
||||
} finally {
|
||||
conn.release();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 2. 특정 날짜의 전체 이슈 목록 조회
|
||||
* 2. 특정 날짜의 전체 이슈 목록 조회 (Promise 기반)
|
||||
*/
|
||||
const getAllByDate = async (date, callback) => {
|
||||
const getAllByDate = async (date) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const [rows] = await db.query(
|
||||
`SELECT
|
||||
d.id,
|
||||
d.date,
|
||||
w.worker_name,
|
||||
p.project_name,
|
||||
d.start_time,
|
||||
d.end_time,
|
||||
t.category,
|
||||
t.subcategory,
|
||||
d.description
|
||||
d.id, d.date, w.worker_name, p.project_name, d.start_time, d.end_time,
|
||||
t.category, t.subcategory, d.description
|
||||
FROM DailyIssueReports d
|
||||
LEFT JOIN Workers w ON d.worker_id = w.worker_id
|
||||
LEFT JOIN Projects p ON d.project_id = p.project_id
|
||||
@@ -54,9 +53,10 @@ const getAllByDate = async (date, callback) => {
|
||||
ORDER BY d.start_time ASC`,
|
||||
[date]
|
||||
);
|
||||
callback(null, rows);
|
||||
return rows;
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
console.error(`[Model] ${date} 이슈 목록 조회 오류:`, err);
|
||||
throw new Error('데이터베이스에서 이슈 목록을 조회하는 중 오류가 발생했습니다.');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -102,22 +102,53 @@ const update = async (id, data, callback) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 5. 삭제
|
||||
* 5. 삭제 (Promise 기반)
|
||||
*/
|
||||
const remove = async (id, callback) => {
|
||||
const remove = async (id) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const [result] = await db.query(`DELETE FROM DailyIssueReports WHERE id = ?`, [id]);
|
||||
callback(null, result.affectedRows);
|
||||
return result.affectedRows;
|
||||
} catch (err) {
|
||||
console.error(`[Model] 이슈(id: ${id}) 삭제 오류:`, err);
|
||||
throw new Error('데이터베이스에서 이슈를 삭제하는 중 오류가 발생했습니다.');
|
||||
}
|
||||
};
|
||||
|
||||
// V1 함수들은 점진적으로 제거 예정
|
||||
const create = async (report, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const {
|
||||
date,
|
||||
worker_id,
|
||||
project_id,
|
||||
start_time,
|
||||
end_time,
|
||||
issue_type_id,
|
||||
description = null // 선택값 처리
|
||||
} = report;
|
||||
|
||||
const [result] = await db.query(
|
||||
`INSERT INTO DailyIssueReports
|
||||
(date, worker_id, project_id, start_time, end_time, issue_type_id, description)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
[date, worker_id, project_id, start_time, end_time, issue_type_id, description]
|
||||
);
|
||||
|
||||
callback(null, result.insertId);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
module.exports = {
|
||||
create,
|
||||
createMany, // 신규
|
||||
getAllByDate,
|
||||
remove,
|
||||
// 레거시 호환성을 위해 V1 함수들 임시 유지
|
||||
create: (report, callback) => createMany([report]).then(ids => callback(null, ids[0])).catch(err => callback(err)),
|
||||
getById,
|
||||
update,
|
||||
remove
|
||||
};
|
||||
93
api.hyungi.net/services/dailyIssueReportService.js
Normal file
93
api.hyungi.net/services/dailyIssueReportService.js
Normal file
@@ -0,0 +1,93 @@
|
||||
// /services/dailyIssueReportService.js
|
||||
const dailyIssueReportModel = require('../models/dailyIssueReportModel');
|
||||
|
||||
/**
|
||||
* 일일 이슈 보고서를 생성하는 비즈니스 로직을 처리합니다.
|
||||
* 한 번에 여러 작업자에 대해 동일한 이슈를 등록할 수 있습니다.
|
||||
* @param {object} issueData - 컨트롤러에서 전달된 이슈 데이터
|
||||
* @returns {Promise<object>} 생성 결과
|
||||
*/
|
||||
const createDailyIssueReportService = async (issueData) => {
|
||||
const { date, project_id, start_time, end_time, issue_type_id, worker_ids } = issueData;
|
||||
|
||||
// 1. 유효성 검사
|
||||
if (!date || !project_id || !start_time || !end_time || !issue_type_id || !worker_ids) {
|
||||
throw new Error('필수 필드가 누락되었습니다.');
|
||||
}
|
||||
if (!Array.isArray(worker_ids) || worker_ids.length === 0) {
|
||||
throw new Error('worker_ids는 최소 한 명 이상의 작업자를 포함하는 배열이어야 합니다.');
|
||||
}
|
||||
|
||||
// 2. 모델에 전달할 데이터 준비
|
||||
const reportsToCreate = worker_ids.map(worker_id => ({
|
||||
date,
|
||||
project_id,
|
||||
start_time,
|
||||
end_time,
|
||||
issue_type_id,
|
||||
worker_id
|
||||
}));
|
||||
|
||||
// 3. 모델 함수 호출 (모델에 createMany와 같은 함수가 필요)
|
||||
try {
|
||||
const insertedIds = await dailyIssueReportModel.createMany(reportsToCreate);
|
||||
return {
|
||||
message: `${insertedIds.length}개의 이슈 보고서가 성공적으로 생성되었습니다.`,
|
||||
issue_report_ids: insertedIds
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[Service] 이슈 보고서 생성 중 오류 발생:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 특정 날짜의 모든 이슈 보고서를 조회합니다.
|
||||
* @param {string} date - 조회할 날짜 (YYYY-MM-DD)
|
||||
* @returns {Promise<Array>} 조회된 이슈 보고서 배열
|
||||
*/
|
||||
const getDailyIssuesByDateService = async (date) => {
|
||||
if (!date) {
|
||||
throw new Error('조회를 위해 날짜(date)는 필수입니다.');
|
||||
}
|
||||
try {
|
||||
const issues = await dailyIssueReportModel.getAllByDate(date);
|
||||
return issues;
|
||||
} catch (error) {
|
||||
console.error(`[Service] ${date}의 이슈 보고서 조회 중 오류 발생:`, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 특정 ID의 이슈 보고서를 삭제합니다.
|
||||
* @param {string} issueId - 삭제할 이슈 보고서의 ID
|
||||
* @returns {Promise<object>} 삭제 결과
|
||||
*/
|
||||
const removeDailyIssueService = async (issueId) => {
|
||||
if (!issueId) {
|
||||
throw new Error('삭제를 위해 이슈 보고서 ID가 필요합니다.');
|
||||
}
|
||||
try {
|
||||
const affectedRows = await dailyIssueReportModel.remove(issueId);
|
||||
if (affectedRows === 0) {
|
||||
const notFoundError = new Error('삭제할 이슈 보고서를 찾을 수 없습니다.');
|
||||
notFoundError.statusCode = 404;
|
||||
throw notFoundError;
|
||||
}
|
||||
return {
|
||||
message: '이슈 보고서가 성공적으로 삭제되었습니다.',
|
||||
deleted_id: issueId,
|
||||
affected_rows: affectedRows
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`[Service] 이슈 보고서(id: ${issueId}) 삭제 중 오류 발생:`, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
createDailyIssueReportService,
|
||||
getDailyIssuesByDateService,
|
||||
removeDailyIssueService,
|
||||
};
|
||||
Reference in New Issue
Block a user