diff --git a/api.hyungi.net/controllers/issueTypeController.js b/api.hyungi.net/controllers/issueTypeController.js index 49ed0d3..96cb434 100644 --- a/api.hyungi.net/controllers/issueTypeController.js +++ b/api.hyungi.net/controllers/issueTypeController.js @@ -1,55 +1,65 @@ -const issueTypeModel = require('../models/issueTypeModel'); +/** + * 이슈 유형 관리 컨트롤러 + * + * 이슈 유형(카테고리/서브카테고리) CRUD API 엔드포인트 핸들러 + * + * @author TK-FB-Project + * @since 2025-12-11 + */ -exports.createIssueType = async (req, res) => { - try { - const id = await new Promise((resolve, reject) => { - issueTypeModel.create(req.body, (err, insertId) => - err ? reject(err) : resolve(insertId) - ); - }); - res.json({ success: true, issue_type_id: id }); - } catch (err) { - res.status(500).json({ error: err.message || String(err) }); - } -}; +const issueTypeService = require('../services/issueTypeService'); +const { asyncHandler } = require('../middlewares/errorHandler'); -exports.getAllIssueTypes = async (_req, res) => { - try { - const rows = await new Promise((resolve, reject) => { - issueTypeModel.getAll((err, data) => err ? reject(err) : resolve(data)); - }); - res.json(rows); - } catch (err) { - res.status(500).json({ error: err.message || String(err) }); - } -}; +/** + * 이슈 유형 생성 + */ +exports.createIssueType = asyncHandler(async (req, res) => { + const result = await issueTypeService.createIssueTypeService(req.body); -exports.updateIssueType = async (req, res) => { - try { - const id = parseInt(req.params.id, 10); - const changes = await new Promise((resolve, reject) => { - issueTypeModel.update(id, req.body, (err, affectedRows) => - err ? reject(err) : resolve(affectedRows) - ); - }); - if (changes === 0) return res.status(404).json({ error: 'Not found or no changes' }); - res.json({ success: true, changes }); - } catch (err) { - res.status(500).json({ error: err.message || String(err) }); - } -}; + res.status(201).json({ + success: true, + data: result, + message: '이슈 유형이 성공적으로 생성되었습니다' + }); +}); -exports.removeIssueType = async (req, res) => { - try { - const id = parseInt(req.params.id, 10); - const changes = await new Promise((resolve, reject) => { - issueTypeModel.remove(id, (err, affectedRows) => - err ? reject(err) : resolve(affectedRows) - ); - }); - if (changes === 0) return res.status(404).json({ error: 'Not found' }); - res.json({ success: true, changes }); - } catch (err) { - res.status(500).json({ error: err.message || String(err) }); - } -}; \ No newline at end of file +/** + * 전체 이슈 유형 조회 + */ +exports.getAllIssueTypes = asyncHandler(async (req, res) => { + const rows = await issueTypeService.getAllIssueTypesService(); + + res.json({ + success: true, + data: rows, + message: '이슈 유형 목록 조회 성공' + }); +}); + +/** + * 이슈 유형 수정 + */ +exports.updateIssueType = asyncHandler(async (req, res) => { + const id = parseInt(req.params.id, 10); + const result = await issueTypeService.updateIssueTypeService(id, req.body); + + res.json({ + success: true, + data: result, + message: '이슈 유형이 성공적으로 수정되었습니다' + }); +}); + +/** + * 이슈 유형 삭제 + */ +exports.removeIssueType = asyncHandler(async (req, res) => { + const id = parseInt(req.params.id, 10); + const result = await issueTypeService.removeIssueTypeService(id); + + res.json({ + success: true, + data: result, + message: '이슈 유형이 성공적으로 삭제되었습니다' + }); +}); diff --git a/api.hyungi.net/controllers/toolsController.js b/api.hyungi.net/controllers/toolsController.js index ef6875f..3480886 100644 --- a/api.hyungi.net/controllers/toolsController.js +++ b/api.hyungi.net/controllers/toolsController.js @@ -1,76 +1,75 @@ -const Tools = require('../models/toolsModel'); +/** + * 도구 관리 컨트롤러 + * + * 도구(공구) 재고 및 위치 관리 API 엔드포인트 핸들러 + * + * @author TK-FB-Project + * @since 2025-12-11 + */ -// 1. 전체 도구 조회 -exports.getAll = async (req, res) => { - try { - const rows = await new Promise((resolve, reject) => { - Tools.getAllTools((err, data) => err ? reject(err) : resolve(data)); - }); - res.json(rows); - } catch (err) { - res.status(500).json({ error: err.message || String(err) }); - } -}; +const toolsService = require('../services/toolsService'); +const { asyncHandler } = require('../middlewares/errorHandler'); -// 2. 단일 도구 조회 -exports.getById = async (req, res) => { - try { - const id = parseInt(req.params.id, 10); - const row = await new Promise((resolve, reject) => { - Tools.getToolById(id, (err, data) => err ? reject(err) : resolve(data)); - }); - if (!row) return res.status(404).json({ error: 'Tool not found' }); - res.json(row); - } catch (err) { - res.status(500).json({ error: err.message || String(err) }); - } -}; +/** + * 전체 도구 조회 + */ +exports.getAll = asyncHandler(async (req, res) => { + const rows = await toolsService.getAllToolsService(); -// 3. 도구 생성 -exports.create = async (req, res) => { - try { - const insertId = await new Promise((resolve, reject) => { - Tools.createTool(req.body, (err, resultId) => { - if (err) return reject(err); - resolve(resultId); - }); - }); - res.status(201).json({ success: true, id: insertId }); - } catch (err) { - res.status(500).json({ error: err.message || String(err) }); - } -}; + res.json({ + success: true, + data: rows, + message: '도구 목록 조회 성공' + }); +}); -// 4. 도구 수정 -exports.update = async (req, res) => { - try { - const id = parseInt(req.params.id, 10); - const changedRows = await new Promise((resolve, reject) => { - Tools.updateTool(id, req.body, (err, affectedRows) => { - if (err) return reject(err); - resolve(affectedRows); - }); - }); - if (changedRows === 0) return res.status(404).json({ error: 'Tool not found or no change' }); - res.json({ success: true, changes: changedRows }); - } catch (err) { - res.status(500).json({ error: err.message || String(err) }); - } -}; +/** + * 단일 도구 조회 + */ +exports.getById = asyncHandler(async (req, res) => { + const id = parseInt(req.params.id, 10); + const row = await toolsService.getToolByIdService(id); -// 5. 도구 삭제 -exports.delete = async (req, res) => { - try { - const id = parseInt(req.params.id, 10); - const deletedRows = await new Promise((resolve, reject) => { - Tools.deleteTool(id, (err, affectedRows) => { - if (err) return reject(err); - resolve(affectedRows); - }); - }); - if (deletedRows === 0) return res.status(404).json({ error: 'Tool not found' }); - res.status(204).send(); - } catch (err) { - res.status(500).json({ error: err.message || String(err) }); - } -}; \ No newline at end of file + res.json({ + success: true, + data: row, + message: '도구 조회 성공' + }); +}); + +/** + * 도구 생성 + */ +exports.create = asyncHandler(async (req, res) => { + const result = await toolsService.createToolService(req.body); + + res.status(201).json({ + success: true, + data: result, + message: '도구가 성공적으로 생성되었습니다' + }); +}); + +/** + * 도구 수정 + */ +exports.update = asyncHandler(async (req, res) => { + const id = parseInt(req.params.id, 10); + const result = await toolsService.updateToolService(id, req.body); + + res.json({ + success: true, + data: result, + message: '도구 정보가 성공적으로 수정되었습니다' + }); +}); + +/** + * 도구 삭제 + */ +exports.delete = asyncHandler(async (req, res) => { + const id = parseInt(req.params.id, 10); + await toolsService.deleteToolService(id); + + res.status(204).send(); +}); diff --git a/api.hyungi.net/services/issueTypeService.js b/api.hyungi.net/services/issueTypeService.js new file mode 100644 index 0000000..25aa048 --- /dev/null +++ b/api.hyungi.net/services/issueTypeService.js @@ -0,0 +1,169 @@ +/** + * 이슈 유형 관리 서비스 + * + * 이슈 유형(카테고리/서브카테고리) 관련 비즈니스 로직 처리 + * + * @author TK-FB-Project + * @since 2025-12-11 + */ + +const issueTypeModel = require('../models/issueTypeModel'); +const { ValidationError, NotFoundError, DatabaseError } = require('../utils/errors'); +const logger = require('../utils/logger'); + +/** + * 이슈 유형 생성 + */ +const createIssueTypeService = async (issueTypeData) => { + const { category, subcategory } = issueTypeData; + + // 필수 필드 검증 + if (!category || !subcategory) { + throw new ValidationError('카테고리와 서브카테고리가 필요합니다', { + required: ['category', 'subcategory'], + received: { category, subcategory } + }); + } + + logger.info('이슈 유형 생성 요청', { category, subcategory }); + + try { + const insertId = await new Promise((resolve, reject) => { + issueTypeModel.create({ category, subcategory }, (err, id) => { + if (err) reject(err); + else resolve(id); + }); + }); + + logger.info('이슈 유형 생성 성공', { issue_type_id: insertId }); + + return { issue_type_id: insertId }; + } catch (error) { + logger.error('이슈 유형 생성 실패', { + category, + subcategory, + error: error.message + }); + throw new DatabaseError('이슈 유형 생성 중 오류가 발생했습니다'); + } +}; + +/** + * 전체 이슈 유형 조회 + */ +const getAllIssueTypesService = async () => { + logger.info('이슈 유형 목록 조회 요청'); + + try { + const rows = await new Promise((resolve, reject) => { + issueTypeModel.getAll((err, data) => { + if (err) reject(err); + else resolve(data); + }); + }); + + logger.info('이슈 유형 목록 조회 성공', { count: rows.length }); + + return rows; + } catch (error) { + logger.error('이슈 유형 목록 조회 실패', { error: error.message }); + throw new DatabaseError('이슈 유형 목록 조회 중 오류가 발생했습니다'); + } +}; + +/** + * 이슈 유형 수정 + */ +const updateIssueTypeService = async (id, issueTypeData) => { + const { category, subcategory } = issueTypeData; + + // ID 검증 + if (!id || isNaN(id)) { + throw new ValidationError('유효하지 않은 이슈 유형 ID입니다'); + } + + // 필수 필드 검증 + if (!category || !subcategory) { + throw new ValidationError('카테고리와 서브카테고리가 필요합니다', { + required: ['category', 'subcategory'], + received: { category, subcategory } + }); + } + + logger.info('이슈 유형 수정 요청', { issue_type_id: id, category, subcategory }); + + try { + const affectedRows = await new Promise((resolve, reject) => { + issueTypeModel.update(id, { category, subcategory }, (err, rows) => { + if (err) reject(err); + else resolve(rows); + }); + }); + + if (affectedRows === 0) { + logger.warn('이슈 유형을 찾을 수 없음', { issue_type_id: id }); + throw new NotFoundError('이슈 유형을 찾을 수 없습니다'); + } + + logger.info('이슈 유형 수정 성공', { issue_type_id: id, affectedRows }); + + return { changes: affectedRows }; + } catch (error) { + if (error instanceof NotFoundError) { + throw error; + } + + logger.error('이슈 유형 수정 실패', { + issue_type_id: id, + error: error.message + }); + throw new DatabaseError('이슈 유형 수정 중 오류가 발생했습니다'); + } +}; + +/** + * 이슈 유형 삭제 + */ +const removeIssueTypeService = async (id) => { + // ID 검증 + if (!id || isNaN(id)) { + throw new ValidationError('유효하지 않은 이슈 유형 ID입니다'); + } + + logger.info('이슈 유형 삭제 요청', { issue_type_id: id }); + + try { + const affectedRows = await new Promise((resolve, reject) => { + issueTypeModel.remove(id, (err, rows) => { + if (err) reject(err); + else resolve(rows); + }); + }); + + if (affectedRows === 0) { + logger.warn('이슈 유형을 찾을 수 없음', { issue_type_id: id }); + throw new NotFoundError('이슈 유형을 찾을 수 없습니다'); + } + + logger.info('이슈 유형 삭제 성공', { issue_type_id: id, affectedRows }); + + return { changes: affectedRows }; + } catch (error) { + if (error instanceof NotFoundError) { + throw error; + } + + logger.error('이슈 유형 삭제 실패', { + issue_type_id: id, + error: error.message + }); + throw new DatabaseError('이슈 유형 삭제 중 오류가 발생했습니다'); + } +}; + +module.exports = { + createIssueTypeService, + getAllIssueTypesService, + updateIssueTypeService, + removeIssueTypeService +}; diff --git a/api.hyungi.net/services/toolsService.js b/api.hyungi.net/services/toolsService.js new file mode 100644 index 0000000..ac5613e --- /dev/null +++ b/api.hyungi.net/services/toolsService.js @@ -0,0 +1,196 @@ +/** + * 도구 관리 서비스 + * + * 도구(공구) 재고 및 위치 관리 관련 비즈니스 로직 처리 + * + * @author TK-FB-Project + * @since 2025-12-11 + */ + +const toolsModel = require('../models/toolsModel'); +const { ValidationError, NotFoundError, DatabaseError } = require('../utils/errors'); +const logger = require('../utils/logger'); + +/** + * 전체 도구 조회 + */ +const getAllToolsService = async () => { + logger.info('도구 목록 조회 요청'); + + try { + const rows = await new Promise((resolve, reject) => { + toolsModel.getAll((err, data) => { + if (err) reject(err); + else resolve(data); + }); + }); + + logger.info('도구 목록 조회 성공', { count: rows.length }); + + return rows; + } catch (error) { + logger.error('도구 목록 조회 실패', { error: error.message }); + throw new DatabaseError('도구 목록 조회 중 오류가 발생했습니다'); + } +}; + +/** + * 단일 도구 조회 + */ +const getToolByIdService = async (id) => { + // ID 검증 + if (!id || isNaN(id)) { + throw new ValidationError('유효하지 않은 도구 ID입니다'); + } + + logger.info('도구 조회 요청', { tool_id: id }); + + try { + const row = await new Promise((resolve, reject) => { + toolsModel.getById(id, (err, data) => { + if (err) reject(err); + else resolve(data); + }); + }); + + if (!row) { + logger.warn('도구를 찾을 수 없음', { tool_id: id }); + throw new NotFoundError('도구를 찾을 수 없습니다'); + } + + logger.info('도구 조회 성공', { tool_id: id }); + + return row; + } catch (error) { + if (error instanceof NotFoundError) { + throw error; + } + + logger.error('도구 조회 실패', { tool_id: id, error: error.message }); + throw new DatabaseError('도구 조회 중 오류가 발생했습니다'); + } +}; + +/** + * 도구 생성 + */ +const createToolService = async (toolData) => { + const { name, location, stock, status, factory_id } = toolData; + + // 필수 필드 검증 + if (!name) { + throw new ValidationError('도구 이름이 필요합니다', { + required: ['name'], + received: { name } + }); + } + + logger.info('도구 생성 요청', { name, location, stock, status }); + + try { + const insertId = await new Promise((resolve, reject) => { + toolsModel.create(toolData, (err, id) => { + if (err) reject(err); + else resolve(id); + }); + }); + + logger.info('도구 생성 성공', { tool_id: insertId, name }); + + return { tool_id: insertId }; + } catch (error) { + logger.error('도구 생성 실패', { + name, + error: error.message + }); + throw new DatabaseError('도구 생성 중 오류가 발생했습니다'); + } +}; + +/** + * 도구 수정 + */ +const updateToolService = async (id, toolData) => { + // ID 검증 + if (!id || isNaN(id)) { + throw new ValidationError('유효하지 않은 도구 ID입니다'); + } + + logger.info('도구 수정 요청', { tool_id: id, updates: toolData }); + + try { + const affectedRows = await new Promise((resolve, reject) => { + toolsModel.update(id, toolData, (err, rows) => { + if (err) reject(err); + else resolve(rows); + }); + }); + + if (affectedRows === 0) { + logger.warn('도구를 찾을 수 없거나 변경사항 없음', { tool_id: id }); + throw new NotFoundError('도구를 찾을 수 없습니다'); + } + + logger.info('도구 수정 성공', { tool_id: id, affectedRows }); + + return { changes: affectedRows }; + } catch (error) { + if (error instanceof NotFoundError) { + throw error; + } + + logger.error('도구 수정 실패', { + tool_id: id, + error: error.message + }); + throw new DatabaseError('도구 수정 중 오류가 발생했습니다'); + } +}; + +/** + * 도구 삭제 + */ +const deleteToolService = async (id) => { + // ID 검증 + if (!id || isNaN(id)) { + throw new ValidationError('유효하지 않은 도구 ID입니다'); + } + + logger.info('도구 삭제 요청', { tool_id: id }); + + try { + const affectedRows = await new Promise((resolve, reject) => { + toolsModel.remove(id, (err, rows) => { + if (err) reject(err); + else resolve(rows); + }); + }); + + if (affectedRows === 0) { + logger.warn('도구를 찾을 수 없음', { tool_id: id }); + throw new NotFoundError('도구를 찾을 수 없습니다'); + } + + logger.info('도구 삭제 성공', { tool_id: id, affectedRows }); + + return { changes: affectedRows }; + } catch (error) { + if (error instanceof NotFoundError) { + throw error; + } + + logger.error('도구 삭제 실패', { + tool_id: id, + error: error.message + }); + throw new DatabaseError('도구 삭제 중 오류가 발생했습니다'); + } +}; + +module.exports = { + getAllToolsService, + getToolByIdService, + createToolService, + updateToolService, + deleteToolService +};