Files
tk-factory-services/user-management/api/controllers/userController.js
Hyungi Ahn f09aa0875a feat(tkuser): 입사일 자동표시 + 퇴사자 목록 분리 + 퇴사일 관리
- 사용자 추가 시 hire_date 전송 (서울 오늘날짜 기본값)
- resigned_date 컬럼 마이그레이션 + CRUD 지원
- 비활성화(삭제) 시 resigned_date 자동 설정 (COALESCE)
- 활성/비활성 사용자 목록 분리, 퇴사자 접기/펼치기
- 퇴사자 재활성화 기능 (resigned_date 초기화)
- 편집 모달에 퇴사일 필드 추가

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 15:47:14 +09:00

158 lines
4.4 KiB
JavaScript

/**
* User Controller
*
* 사용자 CRUD + 비밀번호 관리
*/
const userModel = require('../models/userModel');
const permissionModel = require('../models/permissionModel');
/**
* GET /api/users - 전체 사용자 목록
*/
async function getUsers(req, res, next) {
try {
const users = await userModel.findAll();
res.json({ success: true, data: users });
} catch (err) {
next(err);
}
}
/**
* POST /api/users - 사용자 생성
*/
async function createUser(req, res, next) {
try {
const { username, password, name, full_name, department, department_id, role, hire_date } = req.body;
if (!username || !password) {
return res.status(400).json({ success: false, error: '사용자명과 비밀번호는 필수입니다' });
}
const existing = await userModel.findByUsername(username);
if (existing) {
return res.status(409).json({ success: false, error: '이미 존재하는 사용자명입니다' });
}
const user = await userModel.create({
username,
password,
name: name || full_name,
department,
department_id: department_id || null,
role,
hire_date: hire_date || null
});
res.status(201).json({ success: true, data: user });
} catch (err) {
next(err);
}
}
/**
* PUT /api/users/:id - 사용자 수정
*/
async function updateUser(req, res, next) {
try {
const userId = parseInt(req.params.id);
const data = { ...req.body };
// full_name → name 매핑
if (data.full_name !== undefined && data.name === undefined) {
data.name = data.full_name;
delete data.full_name;
}
// 부서 변경 감지 → 개인 권한 초기화
if (data.department_id !== undefined) {
const existingUser = await userModel.findById(userId);
if (existingUser && existingUser.department_id !== data.department_id) {
await permissionModel.clearUserPermissionsForDepartmentChange(userId);
}
}
const user = await userModel.update(userId, data);
if (!user) {
return res.status(404).json({ success: false, error: '사용자를 찾을 수 없습니다' });
}
res.json({ success: true, data: user });
} catch (err) {
next(err);
}
}
/**
* DELETE /api/users/:id - 사용자 비활성화
*/
async function deleteUser(req, res, next) {
try {
const userId = parseInt(req.params.id);
await userModel.deleteUser(userId);
res.json({ success: true, message: '사용자가 비활성화되었습니다' });
} catch (err) {
next(err);
}
}
/**
* POST /api/users/:id/reset-password - 비밀번호 초기화 (admin)
*/
async function resetPassword(req, res, next) {
try {
const userId = parseInt(req.params.id);
const { new_password } = req.body;
const password = new_password || process.env.DEFAULT_PASSWORD || 'changeme!1';
const user = await userModel.update(userId, { password });
if (!user) {
return res.status(404).json({ success: false, error: '사용자를 찾을 수 없습니다' });
}
res.json({ success: true, message: '비밀번호가 초기화되었습니다' });
} catch (err) {
next(err);
}
}
/**
* POST /api/users/change-password - 본인 비밀번호 변경
*/
async function changePassword(req, res, next) {
try {
const { current_password, new_password } = req.body;
const userId = req.user.user_id || req.user.id;
if (!current_password || !new_password) {
return res.status(400).json({ success: false, error: '현재 비밀번호와 새 비밀번호를 입력하세요' });
}
if (new_password.length < 6) {
return res.status(400).json({ success: false, error: '새 비밀번호는 최소 6자 이상이어야 합니다' });
}
const user = await userModel.findById(userId);
if (!user) {
return res.status(404).json({ success: false, error: '사용자를 찾을 수 없습니다' });
}
const valid = await userModel.verifyPassword(current_password, user.password_hash);
if (!valid) {
return res.status(401).json({ success: false, error: '현재 비밀번호가 올바르지 않습니다' });
}
await userModel.update(userId, { password: new_password });
res.json({ success: true, message: '비밀번호가 변경되었습니다' });
} catch (err) {
next(err);
}
}
module.exports = {
getUsers,
createUser,
updateUser,
deleteUser,
resetPassword,
changePassword
};