Files
tk-factory-services/system1-factory/api/controllers/workerController.js
Hyungi Ahn 550633b89d feat: 3-System 분리 프로젝트 초기 코드 작성
TK-FB(공장관리+신고)와 M-Project(부적합관리)를 3개 독립 시스템으로
분리하기 위한 전체 코드 구조 작성.
- SSO 인증 서비스 (bcrypt + pbkdf2 이중 해시 지원)
- System 1: 공장관리 (TK-FB 기반, 신고 코드 제거)
- System 2: 신고 (TK-FB에서 workIssue 코드 추출)
- System 3: 부적합관리 (M-Project 기반)
- Gateway 포털 (path-based 라우팅)
- 통합 docker-compose.yml 및 배포 스크립트

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 14:40:11 +09:00

278 lines
8.8 KiB
JavaScript

/**
* 작업자 관리 컨트롤러
*
* 작업자 CRUD API 엔드포인트 핸들러
*
* @author TK-FB-Project
* @since 2025-12-11
*/
const workerModel = require('../models/workerModel');
const { ValidationError, NotFoundError, DatabaseError } = require('../utils/errors');
const { asyncHandler } = require('../middlewares/errorHandler');
const logger = require('../utils/logger');
const cache = require('../utils/cache');
const { optimizedQueries } = require('../utils/queryOptimizer');
const { hangulToRoman, generateUniqueUsername } = require('../utils/hangulToRoman');
const bcrypt = require('bcrypt');
const { getDb } = require('../dbPool');
/**
* 작업자 생성
*/
exports.createWorker = asyncHandler(async (req, res) => {
const workerData = req.body;
const createAccount = req.body.create_account;
logger.info('작업자 생성 요청', { name: workerData.worker_name, create_account: createAccount });
const lastID = await workerModel.create(workerData);
// 계정 생성 요청이 있으면 users 테이블에 계정 생성
if (createAccount && workerData.worker_name) {
try {
const db = await getDb();
const username = await generateUniqueUsername(workerData.worker_name, db);
const hashedPassword = await bcrypt.hash('1234', 10);
// User 역할 조회
const [userRole] = await db.query('SELECT id FROM roles WHERE name = ?', ['User']);
if (userRole && userRole.length > 0) {
await db.query(
`INSERT INTO users (username, password, name, worker_id, role_id, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, NOW(), NOW())`,
[username, hashedPassword, workerData.worker_name, lastID, userRole[0].id]
);
logger.info('작업자 계정 자동 생성 성공', { worker_id: lastID, username });
}
} catch (accountError) {
logger.error('계정 생성 실패 (작업자는 생성됨)', { worker_id: lastID, error: accountError.message });
}
}
// 작업자 관련 캐시 무효화
await cache.invalidateCache.worker();
logger.info('작업자 생성 성공', { worker_id: lastID });
res.status(201).json({
success: true,
data: { worker_id: lastID },
message: '작업자가 성공적으로 생성되었습니다'
});
});
/**
* 전체 작업자 조회 (캐싱 및 페이지네이션 적용)
*/
exports.getAllWorkers = asyncHandler(async (req, res) => {
const { page = 1, limit = 100, search = '', status = '', department_id = null } = req.query;
const cacheKey = cache.createKey('workers', 'list', page, limit, search, status, department_id);
// 캐시에서 조회
const cachedData = await cache.get(cacheKey);
if (cachedData) {
logger.debug('캐시 히트', { cacheKey });
return res.json({
success: true,
data: cachedData.data,
pagination: cachedData.pagination,
message: '작업자 목록 조회 성공 (캐시)'
});
}
// 최적화된 쿼리 사용
const result = await optimizedQueries.getWorkersPaged(page, limit, search, status, department_id);
// 캐시에 저장 (5분)
await cache.set(cacheKey, result, cache.TTL.MEDIUM);
logger.debug('캐시 저장', { cacheKey });
res.json({
success: true,
data: result.data,
pagination: result.pagination,
message: '작업자 목록 조회 성공'
});
});
/**
* 단일 작업자 조회
*/
exports.getWorkerById = asyncHandler(async (req, res) => {
const id = parseInt(req.params.worker_id, 10);
if (isNaN(id)) {
throw new ValidationError('유효하지 않은 작업자 ID입니다');
}
const row = await workerModel.getById(id);
if (!row) {
throw new NotFoundError('작업자를 찾을 수 없습니다');
}
res.json({
success: true,
data: row,
message: '작업자 조회 성공'
});
});
/**
* 작업자 수정
*/
exports.updateWorker = asyncHandler(async (req, res) => {
const id = parseInt(req.params.worker_id, 10);
if (isNaN(id)) {
throw new ValidationError('유효하지 않은 작업자 ID입니다');
}
const workerData = { ...req.body, worker_id: id };
const createAccount = req.body.create_account;
console.log('🔧 작업자 수정 요청:', {
worker_id: id,
받은데이터: req.body,
처리할데이터: workerData,
create_account: createAccount
});
// 먼저 현재 작업자 정보 조회 (계정 여부 확인용)
const currentWorker = await workerModel.getById(id);
if (!currentWorker) {
throw new NotFoundError('작업자를 찾을 수 없습니다');
}
// 작업자 정보 업데이트
const changes = await workerModel.update(workerData);
// 계정 생성/해제 처리
const db = await getDb();
const hasAccount = currentWorker.user_id !== null && currentWorker.user_id !== undefined;
let accountAction = null;
let accountUsername = null;
console.log('🔍 계정 생성 체크:', {
createAccount,
hasAccount,
currentWorker_user_id: currentWorker.user_id,
worker_name: workerData.worker_name
});
if (createAccount && !hasAccount && workerData.worker_name) {
// 계정 생성
console.log('✅ 계정 생성 로직 시작');
try {
console.log('🔑 사용자명 생성 중...');
const username = await generateUniqueUsername(workerData.worker_name, db);
console.log('🔑 생성된 사용자명:', username);
const hashedPassword = await bcrypt.hash('1234', 10);
console.log('🔒 비밀번호 해싱 완료');
// User 역할 조회
console.log('👤 User 역할 조회 중...');
const [userRole] = await db.query('SELECT id FROM roles WHERE name = ?', ['User']);
console.log('👤 User 역할 조회 결과:', userRole);
if (userRole && userRole.length > 0) {
console.log('💾 계정 DB 삽입 시작...');
await db.query(
`INSERT INTO users (username, password, name, worker_id, role_id, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, NOW(), NOW())`,
[username, hashedPassword, workerData.worker_name, id, userRole[0].id]
);
console.log('✅ 계정 DB 삽입 완료');
accountAction = 'created';
accountUsername = username;
logger.info('작업자 계정 생성 성공', { worker_id: id, username });
} else {
console.log('❌ User 역할을 찾을 수 없음');
}
} catch (accountError) {
console.error('❌ 계정 생성 오류:', accountError);
logger.error('계정 생성 실패', { worker_id: id, error: accountError.message });
accountAction = 'failed';
}
} else {
console.log('⏭️ 계정 생성 조건 불만족:', { createAccount, hasAccount, hasWorkerName: !!workerData.worker_name });
}
if (!createAccount && hasAccount) {
// 계정 연동 해제 (users.worker_id = NULL)
try {
await db.query('UPDATE users SET worker_id = NULL WHERE worker_id = ?', [id]);
accountAction = 'unlinked';
logger.info('작업자 계정 연동 해제 성공', { worker_id: id });
} catch (unlinkError) {
logger.error('계정 연동 해제 실패', { worker_id: id, error: unlinkError.message });
accountAction = 'unlink_failed';
}
} else if (createAccount && hasAccount) {
accountAction = 'already_exists';
}
// 작업자 관련 캐시 무효화
logger.info('작업자 수정 후 캐시 무효화', { worker_id: id });
await cache.invalidateCache.worker();
logger.info('작업자 수정 성공', { worker_id: id });
// 응답 메시지 구성
let message = '작업자 정보가 성공적으로 수정되었습니다';
if (accountAction === 'created') {
message += ` (계정 생성 완료: ${accountUsername}, 초기 비밀번호: 1234)`;
} else if (accountAction === 'unlinked') {
message += ' (계정 연동 해제 완료)';
} else if (accountAction === 'already_exists') {
message += ' (이미 계정이 존재합니다)';
} else if (accountAction === 'failed') {
message += ' (계정 생성 실패)';
}
res.json({
success: true,
data: {
changes,
account_action: accountAction,
account_username: accountUsername
},
message
});
});
/**
* 작업자 삭제
*/
exports.removeWorker = asyncHandler(async (req, res) => {
const id = parseInt(req.params.worker_id, 10);
if (isNaN(id)) {
throw new ValidationError('유효하지 않은 작업자 ID입니다');
}
const changes = await workerModel.remove(id);
if (changes === 0) {
throw new NotFoundError('작업자를 찾을 수 없습니다');
}
// 작업자 관련 캐시 무효화
logger.info('작업자 삭제 후 캐시 무효화 시작', { worker_id: id });
await cache.invalidateCache.worker();
await cache.delPattern('workers:*');
await cache.flush();
logger.info('작업자 삭제 후 캐시 무효화 완료', { worker_id: id });
res.json({
success: true,
message: '작업자가 성공적으로 삭제되었습니다'
});
});