/** * 작업장 관리 컨트롤러 * * 작업장 카테고리(공장) 및 작업장 CRUD API 엔드포인트 핸들러 * * @author TK-FB-Project * @since 2026-01-26 */ const workplaceModel = require('../models/workplaceModel'); const { ValidationError, NotFoundError, DatabaseError } = require('../utils/errors'); const { asyncHandler } = require('../middlewares/errorHandler'); const logger = require('../utils/logger'); // ==================== 카테고리(공장) 관련 ==================== /** * 카테고리 생성 */ exports.createCategory = asyncHandler(async (req, res) => { const categoryData = req.body; if (!categoryData.category_name) { throw new ValidationError('카테고리명은 필수 입력 항목입니다'); } logger.info('카테고리 생성 요청', { name: categoryData.category_name }); const id = await new Promise((resolve, reject) => { workplaceModel.createCategory(categoryData, (err, lastID) => { if (err) reject(new DatabaseError('카테고리 생성 중 오류가 발생했습니다')); else resolve(lastID); }); }); logger.info('카테고리 생성 성공', { category_id: id }); res.status(201).json({ success: true, data: { category_id: id }, message: '카테고리가 성공적으로 생성되었습니다' }); }); /** * 전체 카테고리 조회 */ exports.getAllCategories = asyncHandler(async (req, res) => { const rows = await new Promise((resolve, reject) => { workplaceModel.getAllCategories((err, data) => { if (err) reject(new DatabaseError('카테고리 목록 조회 중 오류가 발생했습니다')); else resolve(data); }); }); res.json({ success: true, data: rows, message: '카테고리 목록 조회 성공' }); }); /** * 활성 카테고리만 조회 */ exports.getActiveCategories = asyncHandler(async (req, res) => { const rows = await new Promise((resolve, reject) => { workplaceModel.getActiveCategories((err, data) => { if (err) reject(new DatabaseError('활성 카테고리 목록 조회 중 오류가 발생했습니다')); else resolve(data); }); }); res.json({ success: true, data: rows, message: '활성 카테고리 목록 조회 성공' }); }); /** * 단일 카테고리 조회 */ exports.getCategoryById = asyncHandler(async (req, res) => { const categoryId = req.params.id; const category = await new Promise((resolve, reject) => { workplaceModel.getCategoryById(categoryId, (err, data) => { if (err) reject(new DatabaseError('카테고리 조회 중 오류가 발생했습니다')); else resolve(data); }); }); if (!category) { throw new NotFoundError('카테고리를 찾을 수 없습니다'); } res.json({ success: true, data: category, message: '카테고리 조회 성공' }); }); /** * 카테고리 수정 */ exports.updateCategory = asyncHandler(async (req, res) => { const categoryId = req.params.id; const categoryData = req.body; if (!categoryData.category_name) { throw new ValidationError('카테고리명은 필수 입력 항목입니다'); } logger.info('카테고리 수정 요청', { category_id: categoryId }); // 기존 카테고리 정보 가져오기 const existingCategory = await new Promise((resolve, reject) => { workplaceModel.getCategoryById(categoryId, (err, data) => { if (err) reject(new DatabaseError('카테고리 조회 중 오류가 발생했습니다')); else resolve(data); }); }); if (!existingCategory) { throw new NotFoundError('카테고리를 찾을 수 없습니다'); } // layout_image가 요청에 없거나 null이면 기존 값 보존 const updateData = { ...categoryData, layout_image: (categoryData.layout_image !== undefined && categoryData.layout_image !== null) ? categoryData.layout_image : existingCategory.layout_image }; await new Promise((resolve, reject) => { workplaceModel.updateCategory(categoryId, updateData, (err, result) => { if (err) reject(new DatabaseError('카테고리 수정 중 오류가 발생했습니다')); else resolve(result); }); }); logger.info('카테고리 수정 성공', { category_id: categoryId }); res.json({ success: true, message: '카테고리가 성공적으로 수정되었습니다' }); }); /** * 카테고리 삭제 */ exports.deleteCategory = asyncHandler(async (req, res) => { const categoryId = req.params.id; logger.info('카테고리 삭제 요청', { category_id: categoryId }); await new Promise((resolve, reject) => { workplaceModel.deleteCategory(categoryId, (err, result) => { if (err) reject(new DatabaseError('카테고리 삭제 중 오류가 발생했습니다')); else resolve(result); }); }); logger.info('카테고리 삭제 성공', { category_id: categoryId }); res.json({ success: true, message: '카테고리가 성공적으로 삭제되었습니다' }); }); // ==================== 작업장 관련 ==================== /** * 작업장 생성 */ exports.createWorkplace = asyncHandler(async (req, res) => { const workplaceData = req.body; if (!workplaceData.workplace_name) { throw new ValidationError('작업장명은 필수 입력 항목입니다'); } logger.info('작업장 생성 요청', { name: workplaceData.workplace_name }); const id = await new Promise((resolve, reject) => { workplaceModel.createWorkplace(workplaceData, (err, lastID) => { if (err) reject(new DatabaseError('작업장 생성 중 오류가 발생했습니다')); else resolve(lastID); }); }); logger.info('작업장 생성 성공', { workplace_id: id }); res.status(201).json({ success: true, data: { workplace_id: id }, message: '작업장이 성공적으로 생성되었습니다' }); }); /** * 전체 작업장 조회 */ exports.getAllWorkplaces = asyncHandler(async (req, res) => { const categoryId = req.query.category_id; // 카테고리별 필터링 if (categoryId) { const rows = await new Promise((resolve, reject) => { workplaceModel.getWorkplacesByCategory(categoryId, (err, data) => { if (err) reject(new DatabaseError('작업장 목록 조회 중 오류가 발생했습니다')); else resolve(data); }); }); return res.json({ success: true, data: rows, message: '작업장 목록 조회 성공' }); } // 전체 조회 const rows = await new Promise((resolve, reject) => { workplaceModel.getAllWorkplaces((err, data) => { if (err) reject(new DatabaseError('작업장 목록 조회 중 오류가 발생했습니다')); else resolve(data); }); }); res.json({ success: true, data: rows, message: '작업장 목록 조회 성공' }); }); /** * 활성 작업장만 조회 */ exports.getActiveWorkplaces = asyncHandler(async (req, res) => { const rows = await new Promise((resolve, reject) => { workplaceModel.getActiveWorkplaces((err, data) => { if (err) reject(new DatabaseError('활성 작업장 목록 조회 중 오류가 발생했습니다')); else resolve(data); }); }); res.json({ success: true, data: rows, message: '활성 작업장 목록 조회 성공' }); }); /** * 단일 작업장 조회 */ exports.getWorkplaceById = asyncHandler(async (req, res) => { const workplaceId = req.params.id; const workplace = await new Promise((resolve, reject) => { workplaceModel.getWorkplaceById(workplaceId, (err, data) => { if (err) reject(new DatabaseError('작업장 조회 중 오류가 발생했습니다')); else resolve(data); }); }); if (!workplace) { throw new NotFoundError('작업장을 찾을 수 없습니다'); } res.json({ success: true, data: workplace, message: '작업장 조회 성공' }); }); /** * 작업장 수정 */ exports.updateWorkplace = asyncHandler(async (req, res) => { const workplaceId = req.params.id; const workplaceData = req.body; if (!workplaceData.workplace_name) { throw new ValidationError('작업장명은 필수 입력 항목입니다'); } logger.info('작업장 수정 요청', { workplace_id: workplaceId }); // 기존 작업장 정보 가져오기 const existingWorkplace = await new Promise((resolve, reject) => { workplaceModel.getWorkplaceById(workplaceId, (err, data) => { if (err) reject(new DatabaseError('작업장 조회 중 오류가 발생했습니다')); else resolve(data); }); }); if (!existingWorkplace) { throw new NotFoundError('작업장을 찾을 수 없습니다'); } // layout_image가 요청에 없거나 null이면 기존 값 보존 const updateData = { ...workplaceData, layout_image: (workplaceData.layout_image !== undefined && workplaceData.layout_image !== null) ? workplaceData.layout_image : existingWorkplace.layout_image }; await new Promise((resolve, reject) => { workplaceModel.updateWorkplace(workplaceId, updateData, (err, result) => { if (err) reject(new DatabaseError('작업장 수정 중 오류가 발생했습니다')); else resolve(result); }); }); logger.info('작업장 수정 성공', { workplace_id: workplaceId }); res.json({ success: true, message: '작업장이 성공적으로 수정되었습니다' }); }); /** * 작업장 삭제 */ exports.deleteWorkplace = asyncHandler(async (req, res) => { const workplaceId = req.params.id; logger.info('작업장 삭제 요청', { workplace_id: workplaceId }); await new Promise((resolve, reject) => { workplaceModel.deleteWorkplace(workplaceId, (err, result) => { if (err) reject(new DatabaseError('작업장 삭제 중 오류가 발생했습니다')); else resolve(result); }); }); logger.info('작업장 삭제 성공', { workplace_id: workplaceId }); res.json({ success: true, message: '작업장이 성공적으로 삭제되었습니다' }); }); // ==================== 작업장 지도 영역 관련 ==================== /** * 카테고리 레이아웃 이미지 업로드 */ exports.uploadCategoryLayoutImage = asyncHandler(async (req, res) => { const categoryId = req.params.id; if (!req.file) { throw new ValidationError('이미지 파일이 필요합니다'); } const imagePath = `/uploads/${req.file.filename}`; logger.info('카테고리 레이아웃 이미지 업로드 요청', { category_id: categoryId, path: imagePath }); // 현재 카테고리 정보 가져오기 const category = await new Promise((resolve, reject) => { workplaceModel.getCategoryById(categoryId, (err, data) => { if (err) reject(new DatabaseError('카테고리 조회 중 오류가 발생했습니다')); else resolve(data); }); }); if (!category) { throw new NotFoundError('카테고리를 찾을 수 없습니다'); } // 카테고리 정보 업데이트 (이미지 경로만 변경) const updatedData = { category_name: category.category_name, description: category.description, display_order: category.display_order, is_active: category.is_active, layout_image: imagePath }; await new Promise((resolve, reject) => { workplaceModel.updateCategory(categoryId, updatedData, (err, result) => { if (err) reject(new DatabaseError('이미지 경로 저장 중 오류가 발생했습니다')); else resolve(result); }); }); logger.info('레이아웃 이미지 업로드 성공', { category_id: categoryId }); res.json({ success: true, data: { image_path: imagePath }, message: '레이아웃 이미지가 성공적으로 업로드되었습니다' }); }); /** * 작업장 레이아웃 이미지 업로드 */ exports.uploadWorkplaceLayoutImage = asyncHandler(async (req, res) => { const workplaceId = req.params.id; if (!req.file) { throw new ValidationError('이미지 파일이 필요합니다'); } const imagePath = `/uploads/${req.file.filename}`; logger.info('작업장 레이아웃 이미지 업로드 요청', { workplace_id: workplaceId, path: imagePath }); // 현재 작업장 정보 가져오기 const workplace = await new Promise((resolve, reject) => { workplaceModel.getWorkplaceById(workplaceId, (err, data) => { if (err) reject(new DatabaseError('작업장 조회 중 오류가 발생했습니다')); else resolve(data); }); }); if (!workplace) { throw new NotFoundError('작업장을 찾을 수 없습니다'); } // 작업장 정보 업데이트 (이미지 경로만 변경) const updatedData = { workplace_name: workplace.workplace_name, category_id: workplace.category_id, description: workplace.description, workplace_purpose: workplace.workplace_purpose, display_priority: workplace.display_priority, is_active: workplace.is_active, layout_image: imagePath }; await new Promise((resolve, reject) => { workplaceModel.updateWorkplace(workplaceId, updatedData, (err, result) => { if (err) reject(new DatabaseError('이미지 경로 저장 중 오류가 발생했습니다')); else resolve(result); }); }); logger.info('작업장 레이아웃 이미지 업로드 성공', { workplace_id: workplaceId }); res.json({ success: true, data: { image_path: imagePath }, message: '작업장 레이아웃 이미지가 성공적으로 업로드되었습니다' }); }); /** * 지도 영역 생성 */ exports.createMapRegion = asyncHandler(async (req, res) => { const regionData = req.body; if (!regionData.workplace_id || !regionData.category_id) { throw new ValidationError('작업장 ID와 카테고리 ID는 필수 입력 항목입니다'); } logger.info('지도 영역 생성 요청', { workplace_id: regionData.workplace_id }); const id = await new Promise((resolve, reject) => { workplaceModel.createMapRegion(regionData, (err, lastID) => { if (err) reject(new DatabaseError('지도 영역 생성 중 오류가 발생했습니다')); else resolve(lastID); }); }); logger.info('지도 영역 생성 성공', { region_id: id }); res.status(201).json({ success: true, data: { region_id: id }, message: '지도 영역이 성공적으로 생성되었습니다' }); }); /** * 카테고리별 지도 영역 조회 (작업장 정보 포함) */ exports.getMapRegionsByCategory = asyncHandler(async (req, res) => { const categoryId = req.params.categoryId; const rows = await new Promise((resolve, reject) => { workplaceModel.getMapRegionsByCategory(categoryId, (err, data) => { if (err) reject(new DatabaseError('지도 영역 조회 중 오류가 발생했습니다')); else resolve(data); }); }); res.json({ success: true, data: rows, message: '지도 영역 조회 성공' }); }); /** * 작업장별 지도 영역 조회 */ exports.getMapRegionByWorkplace = asyncHandler(async (req, res) => { const workplaceId = req.params.workplaceId; const region = await new Promise((resolve, reject) => { workplaceModel.getMapRegionByWorkplace(workplaceId, (err, data) => { if (err) reject(new DatabaseError('지도 영역 조회 중 오류가 발생했습니다')); else resolve(data); }); }); res.json({ success: true, data: region, message: '지도 영역 조회 성공' }); }); /** * 지도 영역 수정 */ exports.updateMapRegion = asyncHandler(async (req, res) => { const regionId = req.params.id; const regionData = req.body; logger.info('지도 영역 수정 요청', { region_id: regionId }); await new Promise((resolve, reject) => { workplaceModel.updateMapRegion(regionId, regionData, (err, result) => { if (err) reject(new DatabaseError('지도 영역 수정 중 오류가 발생했습니다')); else resolve(result); }); }); logger.info('지도 영역 수정 성공', { region_id: regionId }); res.json({ success: true, message: '지도 영역이 성공적으로 수정되었습니다' }); }); /** * 지도 영역 삭제 */ exports.deleteMapRegion = asyncHandler(async (req, res) => { const regionId = req.params.id; logger.info('지도 영역 삭제 요청', { region_id: regionId }); await new Promise((resolve, reject) => { workplaceModel.deleteMapRegion(regionId, (err, result) => { if (err) reject(new DatabaseError('지도 영역 삭제 중 오류가 발생했습니다')); else resolve(result); }); }); logger.info('지도 영역 삭제 성공', { region_id: regionId }); res.json({ success: true, message: '지도 영역이 성공적으로 삭제되었습니다' }); });