diff --git a/system1-factory/api/Dockerfile b/system1-factory/api/Dockerfile index 7aa384c..314d33e 100644 --- a/system1-factory/api/Dockerfile +++ b/system1-factory/api/Dockerfile @@ -7,8 +7,11 @@ WORKDIR /usr/src/app # 패키지 파일 복사 (캐싱 최적화) COPY package*.json ./ -# 프로덕션 의존성만 설치 -RUN npm install --omit=dev +# 프로덕션 의존성만 설치 (sharp용 빌드 도구 포함) +RUN apk add --no-cache --virtual .build-deps python3 make g++ && \ + npm install --omit=dev && \ + npm install sharp && \ + apk del .build-deps # 앱 소스 복사 COPY . . diff --git a/system1-factory/api/config/middleware.js b/system1-factory/api/config/middleware.js index 83b8eb6..66de6ce 100644 --- a/system1-factory/api/config/middleware.js +++ b/system1-factory/api/config/middleware.js @@ -64,12 +64,7 @@ function setupMiddlewares(app) { code: 'RATE_LIMIT_EXCEEDED' }, standardHeaders: true, - legacyHeaders: false, - // 인증된 사용자는 더 많은 요청 허용 - skip: (req) => { - // Authorization 헤더가 있으면 Rate Limit 완화 - return req.headers.authorization && req.headers.authorization.startsWith('Bearer '); - } + legacyHeaders: false }); // 로그인 시도 제한 (브루트포스 방지) diff --git a/system1-factory/api/controllers/notificationRecipientController.js b/system1-factory/api/controllers/notificationRecipientController.js index e3b4a00..5f09356 100644 --- a/system1-factory/api/controllers/notificationRecipientController.js +++ b/system1-factory/api/controllers/notificationRecipientController.js @@ -16,13 +16,11 @@ const notificationRecipientController = { // 전체 수신자 목록 (유형별 그룹화) getAll: async (req, res) => { try { - console.log('🔔 알림 수신자 목록 조회 시작'); const recipients = await notificationRecipientModel.getAll(); - console.log('✅ 알림 수신자 목록 조회 완료:', recipients); res.json({ success: true, data: recipients }); } catch (error) { - console.error('❌ 수신자 목록 조회 오류:', error.message); - console.error('❌ 스택:', error.stack); + console.error(' 수신자 목록 조회 오류:', error.message); + console.error(' 스택:', error.stack); res.status(500).json({ success: false, error: '수신자 목록 조회 실패', detail: error.message }); } }, diff --git a/system1-factory/api/controllers/userController.js b/system1-factory/api/controllers/userController.js index 2772976..abf8890 100644 --- a/system1-factory/api/controllers/userController.js +++ b/system1-factory/api/controllers/userController.js @@ -682,8 +682,7 @@ const resetUserPassword = asyncHandler(async (req, res) => { throw new NotFoundError('사용자를 찾을 수 없습니다'); } - // 비밀번호를 000000으로 초기화 - const hashedPassword = await bcrypt.hash('000000', 10); + const hashedPassword = await bcrypt.hash(process.env.DEFAULT_PASSWORD || 'changeme!1', 10); await db.execute( 'UPDATE users SET password = ?, password_changed_at = NULL, updated_at = NOW() WHERE user_id = ?', [hashedPassword, id] diff --git a/system1-factory/api/controllers/workReportAnalysisController.js b/system1-factory/api/controllers/workReportAnalysisController.js index 32020c1..e719b58 100644 --- a/system1-factory/api/controllers/workReportAnalysisController.js +++ b/system1-factory/api/controllers/workReportAnalysisController.js @@ -21,37 +21,32 @@ const getAnalysisFilters = asyncHandler(async (req, res) => { const db = await getDb(); try { - // 프로젝트 목록 - const [projects] = await db.query(` - SELECT DISTINCT p.project_id, p.project_name - FROM projects p - INNER JOIN daily_work_reports dwr ON p.project_id = dwr.project_id - ORDER BY p.project_name - `); - - // 작업자 목록 - const [workers] = await db.query(` - SELECT DISTINCT w.user_id, w.worker_name - FROM workers w - INNER JOIN daily_work_reports dwr ON w.user_id = dwr.user_id - ORDER BY w.worker_name - `); - - // 작업 유형 목록 - const [workTypes] = await db.query(` - SELECT DISTINCT wt.id as work_type_id, wt.name as work_type_name - FROM work_types wt - INNER JOIN daily_work_reports dwr ON wt.id = dwr.work_type_id - ORDER BY wt.name - `); - - // 날짜 범위 - const [dateRange] = await db.query(` - SELECT - MIN(report_date) as min_date, - MAX(report_date) as max_date - FROM daily_work_reports - `); + const [[projects], [workers], [workTypes], [dateRange]] = await Promise.all([ + db.query(` + SELECT DISTINCT p.project_id, p.project_name + FROM projects p + INNER JOIN daily_work_reports dwr ON p.project_id = dwr.project_id + ORDER BY p.project_name + `), + db.query(` + SELECT DISTINCT w.user_id, w.worker_name + FROM workers w + INNER JOIN daily_work_reports dwr ON w.user_id = dwr.user_id + ORDER BY w.worker_name + `), + db.query(` + SELECT DISTINCT wt.id as work_type_id, wt.name as work_type_name + FROM work_types wt + INNER JOIN daily_work_reports dwr ON wt.id = dwr.work_type_id + ORDER BY wt.name + `), + db.query(` + SELECT + MIN(report_date) as min_date, + MAX(report_date) as max_date + FROM daily_work_reports + `), + ]); logger.info('분석 필터 데이터 조회 성공', { projects: projects.length, @@ -131,115 +126,108 @@ const getAnalyticsByPeriod = asyncHandler(async (req, res) => { WHERE ${whereClause} `; - const [overallStats] = await db.query(overallSql, queryParams); - - // 2. 일별 통계 - const dailyStatsSql = ` - SELECT - dwr.report_date, - SUM(dwr.work_hours) as daily_hours, - COUNT(*) as daily_entries, - COUNT(DISTINCT dwr.user_id) as daily_workers - FROM daily_work_reports dwr - WHERE ${whereClause} - GROUP BY dwr.report_date - ORDER BY dwr.report_date ASC - `; - - const [dailyStats] = await db.query(dailyStatsSql, queryParams); - - // 3. 일별 에러 통계 - const dailyErrorStatsSql = ` - SELECT - dwr.report_date, - COUNT(CASE WHEN dwr.error_type_id IS NOT NULL THEN 1 END) as daily_errors, - COUNT(*) as daily_total, - ROUND((COUNT(CASE WHEN dwr.error_type_id IS NOT NULL THEN 1 END) / COUNT(*)) * 100, 2) as daily_error_rate - FROM daily_work_reports dwr - WHERE ${whereClause} - GROUP BY dwr.report_date - ORDER BY dwr.report_date ASC - `; - - const [dailyErrorStats] = await db.query(dailyErrorStatsSql, queryParams); - - // 4. 에러 유형별 분석 - const errorAnalysisSql = ` - SELECT - et.id as error_type_id, - et.name as error_type_name, - COUNT(*) as error_count, - SUM(dwr.work_hours) as error_hours, - ROUND((COUNT(*) / (SELECT COUNT(*) FROM daily_work_reports WHERE error_type_id IS NOT NULL)) * 100, 2) as error_percentage - FROM daily_work_reports dwr - LEFT JOIN error_types et ON dwr.error_type_id = et.id - WHERE ${whereClause} AND dwr.error_type_id IS NOT NULL - GROUP BY et.id, et.name - ORDER BY error_count DESC - `; - - const [errorAnalysis] = await db.query(errorAnalysisSql, queryParams); - - // 5. 작업 유형별 분석 - const workTypeAnalysisSql = ` - SELECT - wt.id as work_type_id, - wt.name as work_type_name, - COUNT(*) as work_count, - SUM(dwr.work_hours) as total_hours, - AVG(dwr.work_hours) as avg_hours, - COUNT(CASE WHEN dwr.error_type_id IS NOT NULL THEN 1 END) as error_count, - ROUND((COUNT(CASE WHEN dwr.error_type_id IS NOT NULL THEN 1 END) / COUNT(*)) * 100, 2) as error_rate - FROM daily_work_reports dwr - LEFT JOIN work_types wt ON dwr.work_type_id = wt.id - WHERE ${whereClause} - GROUP BY wt.id, wt.name - ORDER BY total_hours DESC - `; - - const [workTypeAnalysis] = await db.query(workTypeAnalysisSql, queryParams); - - // 6. 작업자별 성과 분석 - const workerAnalysisSql = ` - SELECT - w.user_id, - w.worker_name, - COUNT(*) as total_entries, - SUM(dwr.work_hours) as total_hours, - AVG(dwr.work_hours) as avg_hours_per_entry, - COUNT(DISTINCT dwr.project_id) as projects_worked, - COUNT(DISTINCT dwr.report_date) as working_days, - COUNT(CASE WHEN dwr.error_type_id IS NOT NULL THEN 1 END) as error_count, - ROUND((COUNT(CASE WHEN dwr.error_type_id IS NOT NULL THEN 1 END) / COUNT(*)) * 100, 2) as error_rate - FROM daily_work_reports dwr - LEFT JOIN workers w ON dwr.user_id = w.user_id - WHERE ${whereClause} - GROUP BY w.user_id, w.worker_name - ORDER BY total_hours DESC - `; - - const [workerAnalysis] = await db.query(workerAnalysisSql, queryParams); - - // 7. 프로젝트별 분석 - const projectAnalysisSql = ` - SELECT - p.project_id, - p.project_name, - COUNT(*) as total_entries, - SUM(dwr.work_hours) as total_hours, - COUNT(DISTINCT dwr.user_id) as workers_count, - COUNT(DISTINCT dwr.report_date) as working_days, - AVG(dwr.work_hours) as avg_hours_per_entry, - COUNT(CASE WHEN dwr.error_type_id IS NOT NULL THEN 1 END) as error_count, - ROUND((COUNT(CASE WHEN dwr.error_type_id IS NOT NULL THEN 1 END) / COUNT(*)) * 100, 2) as error_rate - FROM daily_work_reports dwr - LEFT JOIN projects p ON dwr.project_id = p.project_id - WHERE ${whereClause} - GROUP BY p.project_id, p.project_name - ORDER BY total_hours DESC - `; - - const [projectAnalysis] = await db.query(projectAnalysisSql, queryParams); + const [ + [overallStats], + [dailyStats], + [dailyErrorStats], + [errorAnalysis], + [workTypeAnalysis], + [workerAnalysis], + [projectAnalysis], + ] = await Promise.all([ + // 1. 전체 요약 통계 + db.query(overallSql, queryParams), + // 2. 일별 통계 + db.query(` + SELECT + dwr.report_date, + SUM(dwr.work_hours) as daily_hours, + COUNT(*) as daily_entries, + COUNT(DISTINCT dwr.user_id) as daily_workers + FROM daily_work_reports dwr + WHERE ${whereClause} + GROUP BY dwr.report_date + ORDER BY dwr.report_date ASC + `, queryParams), + // 3. 일별 에러 통계 + db.query(` + SELECT + dwr.report_date, + COUNT(CASE WHEN dwr.error_type_id IS NOT NULL THEN 1 END) as daily_errors, + COUNT(*) as daily_total, + ROUND((COUNT(CASE WHEN dwr.error_type_id IS NOT NULL THEN 1 END) / COUNT(*)) * 100, 2) as daily_error_rate + FROM daily_work_reports dwr + WHERE ${whereClause} + GROUP BY dwr.report_date + ORDER BY dwr.report_date ASC + `, queryParams), + // 4. 에러 유형별 분석 + db.query(` + SELECT + et.id as error_type_id, + et.name as error_type_name, + COUNT(*) as error_count, + SUM(dwr.work_hours) as error_hours, + ROUND((COUNT(*) / (SELECT COUNT(*) FROM daily_work_reports WHERE error_type_id IS NOT NULL)) * 100, 2) as error_percentage + FROM daily_work_reports dwr + LEFT JOIN error_types et ON dwr.error_type_id = et.id + WHERE ${whereClause} AND dwr.error_type_id IS NOT NULL + GROUP BY et.id, et.name + ORDER BY error_count DESC + `, queryParams), + // 5. 작업 유형별 분석 + db.query(` + SELECT + wt.id as work_type_id, + wt.name as work_type_name, + COUNT(*) as work_count, + SUM(dwr.work_hours) as total_hours, + AVG(dwr.work_hours) as avg_hours, + COUNT(CASE WHEN dwr.error_type_id IS NOT NULL THEN 1 END) as error_count, + ROUND((COUNT(CASE WHEN dwr.error_type_id IS NOT NULL THEN 1 END) / COUNT(*)) * 100, 2) as error_rate + FROM daily_work_reports dwr + LEFT JOIN work_types wt ON dwr.work_type_id = wt.id + WHERE ${whereClause} + GROUP BY wt.id, wt.name + ORDER BY total_hours DESC + `, queryParams), + // 6. 작업자별 성과 분석 + db.query(` + SELECT + w.user_id, + w.worker_name, + COUNT(*) as total_entries, + SUM(dwr.work_hours) as total_hours, + AVG(dwr.work_hours) as avg_hours_per_entry, + COUNT(DISTINCT dwr.project_id) as projects_worked, + COUNT(DISTINCT dwr.report_date) as working_days, + COUNT(CASE WHEN dwr.error_type_id IS NOT NULL THEN 1 END) as error_count, + ROUND((COUNT(CASE WHEN dwr.error_type_id IS NOT NULL THEN 1 END) / COUNT(*)) * 100, 2) as error_rate + FROM daily_work_reports dwr + LEFT JOIN workers w ON dwr.user_id = w.user_id + WHERE ${whereClause} + GROUP BY w.user_id, w.worker_name + ORDER BY total_hours DESC + `, queryParams), + // 7. 프로젝트별 분석 + db.query(` + SELECT + p.project_id, + p.project_name, + COUNT(*) as total_entries, + SUM(dwr.work_hours) as total_hours, + COUNT(DISTINCT dwr.user_id) as workers_count, + COUNT(DISTINCT dwr.report_date) as working_days, + AVG(dwr.work_hours) as avg_hours_per_entry, + COUNT(CASE WHEN dwr.error_type_id IS NOT NULL THEN 1 END) as error_count, + ROUND((COUNT(CASE WHEN dwr.error_type_id IS NOT NULL THEN 1 END) / COUNT(*)) * 100, 2) as error_rate + FROM daily_work_reports dwr + LEFT JOIN projects p ON dwr.project_id = p.project_id + WHERE ${whereClause} + GROUP BY p.project_id, p.project_name + ORDER BY total_hours DESC + `, queryParams), + ]); logger.info('기간별 분석 데이터 조회 성공', { start_date, diff --git a/system1-factory/api/controllers/workerController.js b/system1-factory/api/controllers/workerController.js index 2693664..7694c74 100644 --- a/system1-factory/api/controllers/workerController.js +++ b/system1-factory/api/controllers/workerController.js @@ -33,7 +33,7 @@ exports.createWorker = asyncHandler(async (req, res) => { try { const db = await getDb(); const username = await generateUniqueUsername(workerData.worker_name, db); - const hashedPassword = await bcrypt.hash('1234', 10); + const hashedPassword = await bcrypt.hash(process.env.DEFAULT_PASSWORD || 'changeme!1', 10); // User 역할 조회 const [userRole] = await db.query('SELECT id FROM roles WHERE name = ?', ['User']); @@ -139,13 +139,6 @@ exports.updateWorker = asyncHandler(async (req, res) => { const workerData = { ...req.body, user_id: id }; const createAccount = req.body.create_account; - console.log('🔧 작업자 수정 요청:', { - user_id: id, - 받은데이터: req.body, - 처리할데이터: workerData, - create_account: createAccount - }); - // 먼저 현재 작업자 정보 조회 (계정 여부 확인용, user_id 기준) const currentWorker = await workerModel.getByUserId(id); @@ -166,7 +159,7 @@ exports.updateWorker = asyncHandler(async (req, res) => { // 계정 생성 try { const username = await generateUniqueUsername(workerData.worker_name, db); - const hashedPassword = await bcrypt.hash('1234', 10); + const hashedPassword = await bcrypt.hash(process.env.DEFAULT_PASSWORD || 'changeme!1', 10); const [userRole] = await db.query('SELECT id FROM roles WHERE name = ?', ['User']); diff --git a/system1-factory/api/create-attendance-tables.js b/system1-factory/api/create-attendance-tables.js index f9901b7..6eac6e2 100644 --- a/system1-factory/api/create-attendance-tables.js +++ b/system1-factory/api/create-attendance-tables.js @@ -13,10 +13,8 @@ async function createAttendanceTables() { database: 'hyungi' }); - console.log('✅ MySQL 연결 성공'); // 1. 근로 유형 테이블 생성 - console.log('📋 근로 유형 테이블 생성 중...'); await connection.execute(` CREATE TABLE IF NOT EXISTS work_attendance_types ( id INT PRIMARY KEY AUTO_INCREMENT, @@ -30,7 +28,6 @@ async function createAttendanceTables() { `); // 2. 휴가 유형 테이블 생성 - console.log('🏖️ 휴가 유형 테이블 생성 중...'); await connection.execute(` CREATE TABLE IF NOT EXISTS vacation_types ( id INT PRIMARY KEY AUTO_INCREMENT, @@ -45,7 +42,6 @@ async function createAttendanceTables() { `); // 3. 일일 근태 기록 테이블 생성 - console.log('📊 일일 근태 기록 테이블 생성 중...'); await connection.execute(` CREATE TABLE IF NOT EXISTS daily_attendance_records ( id INT PRIMARY KEY AUTO_INCREMENT, @@ -73,7 +69,6 @@ async function createAttendanceTables() { `); // 4. 작업자 휴가 잔여 관리 테이블 생성 - console.log('📅 휴가 잔여 관리 테이블 생성 중...'); await connection.execute(` CREATE TABLE IF NOT EXISTS worker_vacation_balance ( id INT PRIMARY KEY AUTO_INCREMENT, @@ -92,7 +87,6 @@ async function createAttendanceTables() { `); // 5. 기본 데이터 삽입 - console.log('📝 기본 데이터 삽입 중...'); // 근로 유형 기본 데이터 await connection.execute(` @@ -116,7 +110,6 @@ async function createAttendanceTables() { `); // 6. 휴가 전용 작업 유형 추가 - console.log('🏖️ 휴가 전용 작업 유형 추가 중...'); await connection.execute(` INSERT IGNORE INTO work_types (name, description, is_active) VALUES ('휴가', '연차, 반차, 병가 등 휴가 처리용', TRUE) @@ -128,40 +121,31 @@ async function createAttendanceTables() { ALTER TABLE daily_work_reports ADD COLUMN attendance_record_id INT NULL COMMENT '근태 기록 ID' AFTER updated_by `); - console.log('✅ daily_work_reports 테이블에 attendance_record_id 컬럼 추가됨'); } catch (error) { if (error.code !== 'ER_DUP_FIELDNAME') { - console.log('⚠️ attendance_record_id 컬럼 추가 실패:', error.message); } else { - console.log('✅ attendance_record_id 컬럼이 이미 존재함'); } } // 8. 인덱스 추가 try { await connection.execute(`CREATE INDEX idx_attendance_record ON daily_work_reports(attendance_record_id)`); - console.log('✅ attendance_record_id 인덱스 추가됨'); } catch (error) { - console.log('⚠️ 인덱스 추가 실패 (이미 존재할 수 있음):', error.message); } - console.log('🎉 근태 관리 DB 설정 완료!'); console.log(''); - console.log('📋 생성된 테이블:'); console.log(' - work_attendance_types (근로 유형)'); console.log(' - vacation_types (휴가 유형)'); console.log(' - daily_attendance_records (일일 근태 기록)'); console.log(' - worker_vacation_balance (휴가 잔여 관리)'); console.log(''); - console.log('✅ 기본 데이터도 모두 삽입되었습니다.'); } catch (error) { - console.error('❌ DB 설정 중 오류 발생:', error); + console.error(' DB 설정 중 오류 발생:', error); // 다른 연결 정보로 시도 if (error.code === 'ECONNREFUSED' || error.code === 'ER_ACCESS_DENIED_ERROR') { console.log(''); - console.log('💡 다른 DB 연결 정보를 시도해보세요:'); console.log(' - host: localhost 또는 127.0.0.1'); console.log(' - port: 3306 (기본값)'); console.log(' - user: root 또는 다른 사용자'); @@ -181,11 +165,10 @@ async function createAttendanceTables() { if (require.main === module) { createAttendanceTables() .then(() => { - console.log('✅ 설정 완료'); process.exit(0); }) .catch((error) => { - console.error('❌ 설정 실패:', error); + console.error(' 설정 실패:', error); process.exit(1); }); } diff --git a/system1-factory/api/db/migrations/20260119095549_add_worker_display_fields.js b/system1-factory/api/db/migrations/20260119095549_add_worker_display_fields.js index 4cb664d..036be26 100644 --- a/system1-factory/api/db/migrations/20260119095549_add_worker_display_fields.js +++ b/system1-factory/api/db/migrations/20260119095549_add_worker_display_fields.js @@ -11,7 +11,6 @@ exports.up = async function(knex) { .comment('재직 상태 (employed: 재직, resigned: 퇴사)'); }); - console.log('✅ workers 테이블에 employment_status 컬럼 추가 완료'); }; /** @@ -23,5 +22,4 @@ exports.down = async function(knex) { table.dropColumn('employment_status'); }); - console.log('✅ workers 테이블에서 employment_status 컬럼 삭제 완료'); }; diff --git a/system1-factory/api/db/migrations/20260119120000_add_worker_fields.js b/system1-factory/api/db/migrations/20260119120000_add_worker_fields.js index 607564b..0cfd2bb 100644 --- a/system1-factory/api/db/migrations/20260119120000_add_worker_fields.js +++ b/system1-factory/api/db/migrations/20260119120000_add_worker_fields.js @@ -8,7 +8,6 @@ */ exports.up = async function(knex) { - console.log('⏳ Workers 테이블에 salary, base_annual_leave 컬럼 추가 중...'); await knex.schema.alterTable('workers', (table) => { // 급여 정보 (선택 사항, NULL 허용) @@ -18,16 +17,13 @@ exports.up = async function(knex) { table.integer('base_annual_leave').defaultTo(15).notNullable().comment('기본 연차 일수'); }); - console.log('✅ Workers 테이블 컬럼 추가 완료'); }; exports.down = async function(knex) { - console.log('⏳ Workers 테이블에서 salary, base_annual_leave 컬럼 제거 중...'); await knex.schema.alterTable('workers', (table) => { table.dropColumn('salary'); table.dropColumn('base_annual_leave'); }); - console.log('✅ Workers 테이블 컬럼 제거 완료'); }; diff --git a/system1-factory/api/db/migrations/20260119120001_create_attendance_tables.js b/system1-factory/api/db/migrations/20260119120001_create_attendance_tables.js index b94cb86..3f61c12 100644 --- a/system1-factory/api/db/migrations/20260119120001_create_attendance_tables.js +++ b/system1-factory/api/db/migrations/20260119120001_create_attendance_tables.js @@ -10,7 +10,6 @@ */ exports.up = async function(knex) { - console.log('⏳ 출근/근태 관련 테이블 생성 중...'); // 1. 출근 유형 테이블 await knex.schema.createTable('work_attendance_types', (table) => { @@ -22,7 +21,6 @@ exports.up = async function(knex) { table.timestamp('created_at').defaultTo(knex.fn.now()); table.timestamp('updated_at').defaultTo(knex.fn.now()); }); - console.log('✅ work_attendance_types 테이블 생성 완료'); // 초기 데이터 입력 await knex('work_attendance_types').insert([ @@ -32,7 +30,6 @@ exports.up = async function(knex) { { type_code: 'ABSENT', type_name: '결근', description: '무단 결근' }, { type_code: 'VACATION', type_name: '휴가', description: '승인된 휴가' } ]); - console.log('✅ work_attendance_types 초기 데이터 입력 완료'); // 2. 휴가 유형 테이블 await knex.schema.createTable('vacation_types', (table) => { @@ -44,7 +41,6 @@ exports.up = async function(knex) { table.timestamp('created_at').defaultTo(knex.fn.now()); table.timestamp('updated_at').defaultTo(knex.fn.now()); }); - console.log('✅ vacation_types 테이블 생성 완료'); // 초기 데이터 입력 await knex('vacation_types').insert([ @@ -53,7 +49,6 @@ exports.up = async function(knex) { { type_code: 'SICK', type_name: '병가', deduct_days: 1.0 }, { type_code: 'SPECIAL', type_name: '경조사', deduct_days: 0 } ]); - console.log('✅ vacation_types 초기 데이터 입력 완료'); // 3. 일일 출근 기록 테이블 await knex.schema.createTable('daily_attendance_records', (table) => { @@ -78,7 +73,6 @@ exports.up = async function(knex) { table.foreign('vacation_type_id').references('vacation_types.id'); table.foreign('created_by').references('users.user_id'); }); - console.log('✅ daily_attendance_records 테이블 생성 완료'); // 4. 작업자 연차 잔액 테이블 await knex.schema.createTable('worker_vacation_balance', (table) => { @@ -95,18 +89,14 @@ exports.up = async function(knex) { table.unique(['worker_id', 'year']); table.foreign('worker_id').references('workers.worker_id').onDelete('CASCADE'); }); - console.log('✅ worker_vacation_balance 테이블 생성 완료'); - console.log('✅ 모든 출근/근태 관련 테이블 생성 완료'); }; exports.down = async function(knex) { - console.log('⏳ 출근/근태 관련 테이블 제거 중...'); await knex.schema.dropTableIfExists('worker_vacation_balance'); await knex.schema.dropTableIfExists('daily_attendance_records'); await knex.schema.dropTableIfExists('vacation_types'); await knex.schema.dropTableIfExists('work_attendance_types'); - console.log('✅ 모든 출근/근태 관련 테이블 제거 완료'); }; diff --git a/system1-factory/api/db/migrations/20260119120002_create_accounts_for_existing_workers.js b/system1-factory/api/db/migrations/20260119120002_create_accounts_for_existing_workers.js index 6a45e11..82c6177 100644 --- a/system1-factory/api/db/migrations/20260119120002_create_accounts_for_existing_workers.js +++ b/system1-factory/api/db/migrations/20260119120002_create_accounts_for_existing_workers.js @@ -14,7 +14,6 @@ const bcrypt = require('bcrypt'); const { generateUniqueUsername } = require('../../utils/hangulToRoman'); exports.up = async function(knex) { - console.log('⏳ 기존 작업자들에게 계정 자동 생성 중...'); // 1. 계정이 없는 작업자 조회 const workersWithoutAccount = await knex('workers') @@ -28,10 +27,8 @@ exports.up = async function(knex) { 'workers.annual_leave' ); - console.log(`📊 계정이 없는 작업자: ${workersWithoutAccount.length}명`); if (workersWithoutAccount.length === 0) { - console.log('ℹ️ 계정이 필요한 작업자가 없습니다.'); return; } @@ -69,7 +66,6 @@ exports.up = async function(knex) { updated_at: knex.fn.now() }); - console.log(`✅ ${worker.worker_name} (ID: ${worker.worker_id}) → username: ${username}`); successCount++; // 현재 연도 연차 잔액 초기화 @@ -84,21 +80,15 @@ exports.up = async function(knex) { }); } catch (error) { - console.error(`❌ ${worker.worker_name} 계정 생성 실패:`, error.message); + console.error(` ${worker.worker_name} 계정 생성 실패:`, error.message); errorCount++; } } - console.log(`\n📊 작업 완료: 성공 ${successCount}명, 실패 ${errorCount}명`); - console.log(`🔐 초기 비밀번호: ${initialPassword} (모든 계정 공통)`); - console.log('⚠️ 사용자들에게 첫 로그인 후 비밀번호를 변경하도록 안내해주세요!'); }; exports.down = async function(knex) { - console.log('⏳ 자동 생성된 계정 제거 중...'); // 이 마이그레이션으로 생성된 계정은 구분하기 어려우므로 // rollback 시 주의가 필요합니다. - console.log('⚠️ 경고: 이 마이그레이션의 rollback은 권장하지 않습니다.'); - console.log('ℹ️ 필요시 수동으로 users 테이블을 관리하세요.'); }; diff --git a/system1-factory/api/db/migrations/20260119120003_add_guest_role.js b/system1-factory/api/db/migrations/20260119120003_add_guest_role.js index 29a00fb..33d6a96 100644 --- a/system1-factory/api/db/migrations/20260119120003_add_guest_role.js +++ b/system1-factory/api/db/migrations/20260119120003_add_guest_role.js @@ -8,7 +8,6 @@ */ exports.up = async function(knex) { - console.log('⏳ 게스트 역할 추가 중...'); // 1. Guest 역할 추가 const [guestRoleId] = await knex('roles').insert({ @@ -17,7 +16,6 @@ exports.up = async function(knex) { created_at: knex.fn.now(), updated_at: knex.fn.now() }); - console.log(`✅ Guest 역할 추가 완료 (ID: ${guestRoleId})`); // 2. 게스트 전용 페이지 추가 await knex('pages').insert({ @@ -29,13 +27,10 @@ exports.up = async function(knex) { created_at: knex.fn.now(), updated_at: knex.fn.now() }); - console.log('✅ 게스트 전용 페이지 추가 완료 (신고 채널)'); - console.log('✅ 게스트 역할 추가 완료'); }; exports.down = async function(knex) { - console.log('⏳ 게스트 역할 제거 중...'); // 페이지 제거 await knex('pages') @@ -47,5 +42,4 @@ exports.down = async function(knex) { .where('name', 'Guest') .delete(); - console.log('✅ 게스트 역할 제거 완료'); }; diff --git a/system1-factory/api/db/migrations/20260120000000_create_tbm_system.js b/system1-factory/api/db/migrations/20260120000000_create_tbm_system.js index 4ee0a02..5082e07 100644 --- a/system1-factory/api/db/migrations/20260120000000_create_tbm_system.js +++ b/system1-factory/api/db/migrations/20260120000000_create_tbm_system.js @@ -11,7 +11,6 @@ */ exports.up = async function(knex) { - console.log('⏳ TBM 시스템 테이블 생성 중...'); // 1. TBM 세션 테이블 (아침 미팅) await knex.schema.createTable('tbm_sessions', (table) => { @@ -35,7 +34,6 @@ exports.up = async function(knex) { table.foreign('project_id').references('projects.project_id').onDelete('SET NULL'); table.foreign('created_by').references('users.user_id'); }); - console.log('✅ tbm_sessions 테이블 생성 완료'); // 2. TBM 팀 구성 테이블 (리더가 선택한 팀원들) await knex.schema.createTable('tbm_team_assignments', (table) => { @@ -53,7 +51,6 @@ exports.up = async function(knex) { table.foreign('session_id').references('tbm_sessions.session_id').onDelete('CASCADE'); table.foreign('worker_id').references('workers.worker_id'); }); - console.log('✅ tbm_team_assignments 테이블 생성 완료'); // 3. TBM 안전 체크리스트 마스터 테이블 await knex.schema.createTable('tbm_safety_checks', (table) => { @@ -69,7 +66,6 @@ exports.up = async function(knex) { table.index('check_category'); }); - console.log('✅ tbm_safety_checks 테이블 생성 완료'); // 초기 안전 체크리스트 데이터 await knex('tbm_safety_checks').insert([ @@ -97,7 +93,6 @@ exports.up = async function(knex) { { check_category: 'EMERGENCY', check_item: '소화기 위치 확인', display_order: 31, is_required: true }, { check_category: 'EMERGENCY', check_item: '응급처치 키트 위치 확인', display_order: 32, is_required: true }, ]); - console.log('✅ tbm_safety_checks 초기 데이터 입력 완료'); // 4. TBM 안전 체크 기록 테이블 await knex.schema.createTable('tbm_safety_records', (table) => { @@ -115,7 +110,6 @@ exports.up = async function(knex) { table.foreign('check_id').references('tbm_safety_checks.check_id'); table.foreign('checked_by').references('users.user_id'); }); - console.log('✅ tbm_safety_records 테이블 생성 완료'); // 5. 작업 인계 테이블 (반차/조퇴 시) await knex.schema.createTable('team_handovers', (table) => { @@ -140,13 +134,10 @@ exports.up = async function(knex) { table.foreign('to_leader_id').references('workers.worker_id'); table.foreign('confirmed_by').references('users.user_id'); }); - console.log('✅ team_handovers 테이블 생성 완료'); - console.log('✅ 모든 TBM 시스템 테이블 생성 완료'); }; exports.down = async function(knex) { - console.log('⏳ TBM 시스템 테이블 제거 중...'); await knex.schema.dropTableIfExists('team_handovers'); await knex.schema.dropTableIfExists('tbm_safety_records'); @@ -154,5 +145,4 @@ exports.down = async function(knex) { await knex.schema.dropTableIfExists('tbm_team_assignments'); await knex.schema.dropTableIfExists('tbm_sessions'); - console.log('✅ 모든 TBM 시스템 테이블 제거 완료'); }; diff --git a/system1-factory/api/db/migrations/20260120000001_add_tbm_page.js b/system1-factory/api/db/migrations/20260120000001_add_tbm_page.js index 4b3c9bb..d9fd8c3 100644 --- a/system1-factory/api/db/migrations/20260120000001_add_tbm_page.js +++ b/system1-factory/api/db/migrations/20260120000001_add_tbm_page.js @@ -6,7 +6,6 @@ */ exports.up = async function(knex) { - console.log('⏳ TBM 페이지 등록 중...'); // TBM 페이지 추가 await knex('pages').insert([ @@ -21,13 +20,10 @@ exports.up = async function(knex) { } ]); - console.log('✅ TBM 페이지 등록 완료'); }; exports.down = async function(knex) { - console.log('⏳ TBM 페이지 제거 중...'); await knex('pages').where('page_key', 'tbm').del(); - console.log('✅ TBM 페이지 제거 완료'); }; diff --git a/system1-factory/api/db/migrations/20260126010002_create_tasks.js b/system1-factory/api/db/migrations/20260126010002_create_tasks.js index aeb4bfc..c1ec1b7 100644 --- a/system1-factory/api/db/migrations/20260126010002_create_tasks.js +++ b/system1-factory/api/db/migrations/20260126010002_create_tasks.js @@ -24,7 +24,6 @@ exports.up = function(knex) { table.index('work_type_id'); table.index('is_active'); }).then(() => { - console.log('✅ tasks 테이블 생성 완료'); }); }; diff --git a/system1-factory/api/db/migrations/20260126010003_add_work_type_task_to_tbm.js b/system1-factory/api/db/migrations/20260126010003_add_work_type_task_to_tbm.js index 45d11bd..2f152dc 100644 --- a/system1-factory/api/db/migrations/20260126010003_add_work_type_task_to_tbm.js +++ b/system1-factory/api/db/migrations/20260126010003_add_work_type_task_to_tbm.js @@ -25,7 +25,6 @@ exports.up = function(knex) { table.index('work_type_id'); table.index('task_id'); }).then(() => { - console.log('✅ tbm_sessions 테이블에 work_type_id, task_id 컬럼 추가 완료'); }); }; diff --git a/system1-factory/api/db/migrations/20260127040000_update_pages_for_current_system.js b/system1-factory/api/db/migrations/20260127040000_update_pages_for_current_system.js index 4a19ab6..ff8ed87 100644 --- a/system1-factory/api/db/migrations/20260127040000_update_pages_for_current_system.js +++ b/system1-factory/api/db/migrations/20260127040000_update_pages_for_current_system.js @@ -143,10 +143,8 @@ exports.up = async function(knex) { } ]); - console.log('✅ 현재 사용 중인 페이지 목록 업데이트 완료'); }; exports.down = async function(knex) { await knex('pages').del(); - console.log('✅ 페이지 목록 삭제 완료'); }; diff --git a/system1-factory/api/db/migrations/20260128000000_add_layout_image_to_workplaces.js b/system1-factory/api/db/migrations/20260128000000_add_layout_image_to_workplaces.js index c4a5db4..7803296 100644 --- a/system1-factory/api/db/migrations/20260128000000_add_layout_image_to_workplaces.js +++ b/system1-factory/api/db/migrations/20260128000000_add_layout_image_to_workplaces.js @@ -10,7 +10,6 @@ exports.up = async function(knex) { table.string('layout_image', 500).nullable().comment('작업장 레이아웃 이미지 경로'); }); - console.log('✅ workplaces 테이블에 layout_image 컬럼 추가 완료'); }; exports.down = async function(knex) { @@ -18,5 +17,4 @@ exports.down = async function(knex) { table.dropColumn('layout_image'); }); - console.log('✅ workplaces 테이블에서 layout_image 컬럼 제거 완료'); }; diff --git a/system1-factory/api/db/migrations/20260128010000_create_equipments_table.js b/system1-factory/api/db/migrations/20260128010000_create_equipments_table.js index c1fb50b..4abb6a7 100644 --- a/system1-factory/api/db/migrations/20260128010000_create_equipments_table.js +++ b/system1-factory/api/db/migrations/20260128010000_create_equipments_table.js @@ -39,10 +39,8 @@ exports.up = async function(knex) { table.index('status'); }); - console.log('✅ equipments 테이블 생성 완료'); }; exports.down = async function(knex) { await knex.schema.dropTableIfExists('equipments'); - console.log('✅ equipments 테이블 삭제 완료'); }; diff --git a/system1-factory/api/db/migrations/20260129000000_create_vacation_requests.js b/system1-factory/api/db/migrations/20260129000000_create_vacation_requests.js index 3cdc18d..f5af981 100644 --- a/system1-factory/api/db/migrations/20260129000000_create_vacation_requests.js +++ b/system1-factory/api/db/migrations/20260129000000_create_vacation_requests.js @@ -48,10 +48,8 @@ exports.up = async function(knex) { table.index(['start_date', 'end_date'], 'idx_vacation_requests_dates'); }); - console.log('✅ vacation_requests 테이블 생성 완료'); }; exports.down = async function(knex) { await knex.schema.dropTableIfExists('vacation_requests'); - console.log('✅ vacation_requests 테이블 삭제 완료'); }; diff --git a/system1-factory/api/db/migrations/20260129000001_register_attendance_pages.js b/system1-factory/api/db/migrations/20260129000001_register_attendance_pages.js index e9ee5c9..19ede49 100644 --- a/system1-factory/api/db/migrations/20260129000001_register_attendance_pages.js +++ b/system1-factory/api/db/migrations/20260129000001_register_attendance_pages.js @@ -45,7 +45,6 @@ exports.up = async function(knex) { } ]); - console.log('✅ 출퇴근 관리 페이지 4개 등록 완료'); // Admin 사용자(user_id=1)에게 페이지 접근 권한 부여 const adminUserId = 1; @@ -66,7 +65,6 @@ exports.up = async function(knex) { })); await knex('user_page_access').insert(accessRecords); - console.log('✅ Admin 사용자에게 출퇴근 관리 페이지 접근 권한 부여 완료'); }; exports.down = async function(knex) { @@ -80,5 +78,4 @@ exports.down = async function(knex) { ]) .delete(); - console.log('✅ 출퇴근 관리 페이지 삭제 완료'); }; diff --git a/system1-factory/api/db/migrations/20260129000002_add_attendance_is_present.js b/system1-factory/api/db/migrations/20260129000002_add_attendance_is_present.js index 0ebafd3..0e71b5a 100644 --- a/system1-factory/api/db/migrations/20260129000002_add_attendance_is_present.js +++ b/system1-factory/api/db/migrations/20260129000002_add_attendance_is_present.js @@ -18,9 +18,7 @@ exports.up = async function(knex) { .whereNotNull('id') .update({ is_present: true }); - console.log('✅ is_present 컬럼 추가 완료'); } else { - console.log('⏭️ is_present 컬럼이 이미 존재합니다'); } }; diff --git a/system1-factory/api/db/migrations/20260129000003_update_vacation_pages.js b/system1-factory/api/db/migrations/20260129000003_update_vacation_pages.js index 54b57c9..f49e14b 100644 --- a/system1-factory/api/db/migrations/20260129000003_update_vacation_pages.js +++ b/system1-factory/api/db/migrations/20260129000003_update_vacation_pages.js @@ -33,7 +33,6 @@ exports.up = async function(knex) { } ]); - console.log('✅ 휴가 관리 페이지 분리 완료 (기존 1개 → 신규 2개)'); }; exports.down = async function(knex) { @@ -53,5 +52,4 @@ exports.down = async function(knex) { display_order: 50 }); - console.log('✅ 휴가 관리 페이지 롤백 완료'); }; diff --git a/system1-factory/api/db/migrations/20260129000004_extend_vacation_types.js b/system1-factory/api/db/migrations/20260129000004_extend_vacation_types.js index 0672e80..3ee1073 100644 --- a/system1-factory/api/db/migrations/20260129000004_extend_vacation_types.js +++ b/system1-factory/api/db/migrations/20260129000004_extend_vacation_types.js @@ -39,7 +39,6 @@ exports.up = async function(knex) { description: '경조사 휴가 (무급)' }); - console.log('✅ vacation_types 테이블 확장 완료'); }; exports.down = async function(knex) { @@ -51,5 +50,4 @@ exports.down = async function(knex) { table.dropColumn('is_system'); }); - console.log('✅ vacation_types 테이블 롤백 완료'); }; diff --git a/system1-factory/api/db/migrations/20260129000005_create_vacation_balance_details.js b/system1-factory/api/db/migrations/20260129000005_create_vacation_balance_details.js index 75b8f7b..2fa7eac 100644 --- a/system1-factory/api/db/migrations/20260129000005_create_vacation_balance_details.js +++ b/system1-factory/api/db/migrations/20260129000005_create_vacation_balance_details.js @@ -37,7 +37,6 @@ exports.up = async function(knex) { COMMENT '잔여 일수' `); - console.log('✅ vacation_balance_details 테이블 생성 완료'); // 기존 worker_vacation_balance 데이터를 vacation_balance_details로 마이그레이션 const existingBalances = await knex('worker_vacation_balance').select('*'); @@ -75,7 +74,6 @@ exports.up = async function(knex) { await knex('vacation_balance_details').insert(balanceDetails); - console.log(`✅ ${balanceDetails.length}건의 기존 휴가 데이터 마이그레이션 완료`); } }; @@ -83,5 +81,4 @@ exports.down = async function(knex) { // vacation_balance_details 테이블 삭제 await knex.schema.dropTableIfExists('vacation_balance_details'); - console.log('✅ vacation_balance_details 테이블 롤백 완료'); }; diff --git a/system1-factory/api/db/migrations/20260129000006_register_vacation_pages.js b/system1-factory/api/db/migrations/20260129000006_register_vacation_pages.js index 0773a7f..461e140 100644 --- a/system1-factory/api/db/migrations/20260129000006_register_vacation_pages.js +++ b/system1-factory/api/db/migrations/20260129000006_register_vacation_pages.js @@ -26,7 +26,6 @@ exports.up = async function(knex) { } ]); - console.log('✅ 휴가 관리 신규 페이지 2개 등록 완료'); }; exports.down = async function(knex) { @@ -34,5 +33,4 @@ exports.down = async function(knex) { .whereIn('page_key', ['annual-vacation-overview', 'vacation-allocation']) .del(); - console.log('✅ 휴가 관리 페이지 롤백 완료'); }; diff --git a/system1-factory/api/db/migrations/20260129010000_create_visit_request_system.js b/system1-factory/api/db/migrations/20260129010000_create_visit_request_system.js index ae65516..9ccb076 100644 --- a/system1-factory/api/db/migrations/20260129010000_create_visit_request_system.js +++ b/system1-factory/api/db/migrations/20260129010000_create_visit_request_system.js @@ -140,7 +140,6 @@ exports.up = async function(knex) { table.index('request_id', 'idx_request_id'); }); - console.log('✅ 출입 신청 및 안전교육 시스템 테이블 생성 완료'); }; exports.down = async function(knex) { @@ -149,5 +148,4 @@ exports.down = async function(knex) { await knex.schema.dropTableIfExists('workplace_visit_requests'); await knex.schema.dropTableIfExists('visit_purpose_types'); - console.log('✅ 출입 신청 및 안전교육 시스템 테이블 삭제 완료'); }; diff --git a/system1-factory/api/db/migrations/20260129010001_register_visit_pages.js b/system1-factory/api/db/migrations/20260129010001_register_visit_pages.js index 6721083..bbfa93c 100644 --- a/system1-factory/api/db/migrations/20260129010001_register_visit_pages.js +++ b/system1-factory/api/db/migrations/20260129010001_register_visit_pages.js @@ -36,7 +36,6 @@ exports.up = async function(knex) { display_order: 61 }); - console.log('✅ 출입 신청 및 안전관리 페이지 등록 완료'); }; exports.down = async function(knex) { @@ -46,5 +45,4 @@ exports.down = async function(knex) { 'safety-training-conduct' ]).delete(); - console.log('✅ 출입 신청 및 안전관리 페이지 삭제 완료'); }; diff --git a/system1-factory/api/db/migrations/20260130000002_register_issue_pages.js b/system1-factory/api/db/migrations/20260130000002_register_issue_pages.js index 602e1be..190444e 100644 --- a/system1-factory/api/db/migrations/20260130000002_register_issue_pages.js +++ b/system1-factory/api/db/migrations/20260130000002_register_issue_pages.js @@ -36,7 +36,6 @@ exports.up = async function(knex) { display_order: 18 }); - console.log('✅ 문제 신고 페이지 등록 완료'); }; exports.down = async function(knex) { @@ -46,5 +45,4 @@ exports.down = async function(knex) { 'issue-detail' ]).delete(); - console.log('✅ 문제 신고 페이지 삭제 완료'); }; diff --git a/system1-factory/api/db/migrations/20260204100000_add_equipment_purchase_fields.js b/system1-factory/api/db/migrations/20260204100000_add_equipment_purchase_fields.js index ac79357..cb62347 100644 --- a/system1-factory/api/db/migrations/20260204100000_add_equipment_purchase_fields.js +++ b/system1-factory/api/db/migrations/20260204100000_add_equipment_purchase_fields.js @@ -19,9 +19,7 @@ exports.up = async function(knex) { table.decimal('purchase_price', 15, 0).nullable().after('supplier').comment('구입가격'); } }); - console.log('✅ equipments 테이블에 supplier, purchase_price 컬럼 추가 완료'); } else { - console.log('ℹ️ supplier, purchase_price 컬럼이 이미 존재합니다. 스킵합니다.'); } }; @@ -31,5 +29,4 @@ exports.down = async function(knex) { table.dropColumn('purchase_price'); }); - console.log('✅ equipments 테이블에서 supplier, purchase_price 컬럼 삭제 완료'); }; diff --git a/system1-factory/api/db/migrations/20260204100000_create_daily_patrol_system.js b/system1-factory/api/db/migrations/20260204100000_create_daily_patrol_system.js index e5bac4e..86915d5 100644 --- a/system1-factory/api/db/migrations/20260204100000_create_daily_patrol_system.js +++ b/system1-factory/api/db/migrations/20260204100000_create_daily_patrol_system.js @@ -10,7 +10,6 @@ */ exports.up = async function(knex) { - console.log('⏳ 일일순회점검 시스템 테이블 생성 중...'); // 1. 순회점검 체크리스트 마스터 테이블 await knex.schema.createTable('patrol_checklist_items', (table) => { @@ -32,7 +31,6 @@ exports.up = async function(knex) { table.foreign('workplace_id').references('workplaces.workplace_id').onDelete('CASCADE'); table.foreign('category_id').references('workplace_categories.category_id').onDelete('CASCADE'); }); - console.log('✅ patrol_checklist_items 테이블 생성 완료'); // 초기 순회점검 체크리스트 데이터 await knex('patrol_checklist_items').insert([ @@ -58,7 +56,6 @@ exports.up = async function(knex) { { check_category: 'ENVIRONMENT', check_item: '환기 상태', display_order: 31, is_required: true }, { check_category: 'ENVIRONMENT', check_item: '누수/누유 여부', display_order: 32, is_required: true }, ]); - console.log('✅ patrol_checklist_items 초기 데이터 입력 완료'); // 2. 순회점검 세션 테이블 await knex.schema.createTable('daily_patrol_sessions', (table) => { @@ -80,7 +77,6 @@ exports.up = async function(knex) { table.foreign('inspector_id').references('users.user_id'); table.foreign('category_id').references('workplace_categories.category_id').onDelete('SET NULL'); }); - console.log('✅ daily_patrol_sessions 테이블 생성 완료'); // 3. 순회점검 체크 기록 테이블 await knex.schema.createTable('patrol_check_records', (table) => { @@ -100,7 +96,6 @@ exports.up = async function(knex) { table.foreign('workplace_id').references('workplaces.workplace_id').onDelete('CASCADE'); table.foreign('check_item_id').references('patrol_checklist_items.item_id').onDelete('CASCADE'); }); - console.log('✅ patrol_check_records 테이블 생성 완료'); // 4. 작업장 물품 현황 테이블 await knex.schema.createTable('workplace_items', (table) => { @@ -129,7 +124,6 @@ exports.up = async function(knex) { table.foreign('created_by').references('users.user_id'); table.foreign('updated_by').references('users.user_id'); }); - console.log('✅ workplace_items 테이블 생성 완료'); // 물품 유형 코드 테이블 (선택적 확장용) await knex.schema.createTable('item_types', (table) => { @@ -148,13 +142,10 @@ exports.up = async function(knex) { { type_code: 'tool', type_name: '공구/장비', icon: '🔧', color: '#8b5cf6', display_order: 4 }, { type_code: 'other', type_name: '기타', icon: '📍', color: '#6b7280', display_order: 5 }, ]); - console.log('✅ item_types 테이블 생성 및 초기 데이터 완료'); - console.log('✅ 모든 일일순회점검 시스템 테이블 생성 완료'); }; exports.down = async function(knex) { - console.log('⏳ 일일순회점검 시스템 테이블 제거 중...'); await knex.schema.dropTableIfExists('item_types'); await knex.schema.dropTableIfExists('workplace_items'); @@ -162,5 +153,4 @@ exports.down = async function(knex) { await knex.schema.dropTableIfExists('daily_patrol_sessions'); await knex.schema.dropTableIfExists('patrol_checklist_items'); - console.log('✅ 모든 일일순회점검 시스템 테이블 제거 완료'); }; diff --git a/system1-factory/api/db/migrations/20260206_migrate_error_types_to_issue_items.js b/system1-factory/api/db/migrations/20260206_migrate_error_types_to_issue_items.js index 18659f1..e473e51 100644 --- a/system1-factory/api/db/migrations/20260206_migrate_error_types_to_issue_items.js +++ b/system1-factory/api/db/migrations/20260206_migrate_error_types_to_issue_items.js @@ -98,7 +98,6 @@ exports.up = async function(knex) { ]); if (unmapped.length > 0) { - console.log('⚠️ 매핑되지 않은 error_type_id 발견:', unmapped); console.log(' 이 데이터는 수동으로 확인 필요'); } @@ -107,6 +106,5 @@ exports.up = async function(knex) { exports.down = async function(knex) { // 롤백은 복잡하므로 로그만 출력 - console.log('⚠️ 이 마이그레이션은 자동 롤백을 지원하지 않습니다.'); console.log(' 데이터 복구가 필요한 경우 백업에서 복원해주세요.'); }; diff --git a/system1-factory/api/db/migrations/20260212_add_department_to_users.js b/system1-factory/api/db/migrations/20260212_add_department_to_users.js index f2f2954..9e538dc 100644 --- a/system1-factory/api/db/migrations/20260212_add_department_to_users.js +++ b/system1-factory/api/db/migrations/20260212_add_department_to_users.js @@ -22,9 +22,7 @@ exports.up = async function(knex) { WHERE u.worker_id IS NOT NULL AND w.department_id IS NOT NULL `); - console.log('✅ users.department_id 컬럼 추가 및 기존 데이터 backfill 완료'); } else { - console.log('⏭️ users.department_id 컬럼이 이미 존재합니다'); } }; diff --git a/system1-factory/api/db/migrations/20260305000001_worker_id_to_user_id_migration.js b/system1-factory/api/db/migrations/20260305000001_worker_id_to_user_id_migration.js index f5c675a..d99490d 100644 --- a/system1-factory/api/db/migrations/20260305000001_worker_id_to_user_id_migration.js +++ b/system1-factory/api/db/migrations/20260305000001_worker_id_to_user_id_migration.js @@ -23,7 +23,6 @@ exports.up = async function(knex) { table.boolean('is_production').defaultTo(false).comment('생산직 부서 여부'); }); await knex.raw(`UPDATE departments SET is_production = TRUE WHERE department_name LIKE '%생산%'`); - console.log('✅ departments.is_production 추가 완료'); } // ============================================================ @@ -41,7 +40,6 @@ exports.up = async function(knex) { SET s.department_id = d.department_id WHERE s.department IS NOT NULL `); - console.log('✅ sso_users.department_id 추가 및 백필 완료'); } // ============================================================ @@ -62,7 +60,6 @@ exports.up = async function(knex) { `); // user_id에 인덱스 추가 await knex.raw(`ALTER TABLE workers ADD INDEX idx_workers_user_id (user_id)`); - console.log('✅ workers.user_id 추가 및 백필 완료'); } // ============================================================ @@ -85,7 +82,6 @@ exports.up = async function(knex) { for (const tableName of tablesWithWorkerId) { const tableExists = await knex.schema.hasTable(tableName); if (!tableExists) { - console.log(`⏭️ ${tableName} 테이블이 존재하지 않음, 건너뜀`); continue; } @@ -103,7 +99,6 @@ exports.up = async function(knex) { `); // 인덱스 추가 await knex.raw(`ALTER TABLE ${tableName} ADD INDEX idx_${tableName}_user_id (user_id)`); - console.log(`✅ ${tableName}.user_id 추가 및 백필 완료`); } } @@ -121,7 +116,6 @@ exports.up = async function(knex) { SET t.user_id = w.user_id WHERE w.user_id IS NOT NULL `); - console.log('✅ DailyIssueReports.user_id 추가 및 백필 완료'); } } @@ -139,7 +133,6 @@ exports.up = async function(knex) { SET t.user_id = w.user_id WHERE w.user_id IS NOT NULL `); - console.log('✅ WorkReports.user_id 추가 및 백필 완료'); } } @@ -156,7 +149,6 @@ exports.up = async function(knex) { WHERE w.user_id IS NOT NULL `); await knex.raw(`ALTER TABLE tbm_sessions ADD INDEX idx_tbm_sessions_leader_user_id (leader_user_id)`); - console.log('✅ tbm_sessions.leader_user_id 추가 및 백필 완료'); } // team_handovers: from/to_leader_id → from/to_leader_user_id 추가 @@ -178,10 +170,8 @@ exports.up = async function(knex) { SET t.to_leader_user_id = w2.user_id WHERE w2.user_id IS NOT NULL `); - console.log('✅ team_handovers.from/to_leader_user_id 추가 및 백필 완료'); } - console.log('🎉 Phase 1 마이그레이션 완료: 모든 테이블에 user_id 컬럼 추가 및 백필 완료'); }; exports.down = async function(knex) { @@ -211,7 +201,6 @@ exports.down = async function(knex) { await knex.schema.table(tableName, (table) => { table.dropColumn(columnName); }); - console.log(`↩️ ${tableName}.${columnName} 제거`); } } @@ -224,7 +213,6 @@ exports.down = async function(knex) { await knex.schema.table(tableName, (table) => { table.dropColumn('user_id'); }); - console.log(`↩️ ${tableName}.user_id 제거`); } } } diff --git a/system1-factory/api/db/scripts/20260205_fix_work_type_id_data.js b/system1-factory/api/db/scripts/20260205_fix_work_type_id_data.js index 53c520a..5926650 100644 --- a/system1-factory/api/db/scripts/20260205_fix_work_type_id_data.js +++ b/system1-factory/api/db/scripts/20260205_fix_work_type_id_data.js @@ -12,7 +12,6 @@ const { getDb } = require('../../dbPool'); async function migrate() { const db = await getDb(); - console.log('🔄 TBM 기반 작업보고서 work_type_id 수정 시작...\n'); try { // 1. 수정 대상 확인 (TBM 기반이면서 work_type_id가 task_id와 다른 경우) @@ -33,15 +32,12 @@ async function migrate() { ORDER BY dwr.report_date DESC `); - console.log(`📊 수정 대상: ${checkResult.length}개 레코드\n`); if (checkResult.length === 0) { - console.log('✅ 수정할 데이터가 없습니다.'); return; } // 수정 대상 샘플 출력 - console.log('📋 수정 대상 샘플 (최대 10개):'); console.log('─'.repeat(80)); checkResult.slice(0, 10).forEach(row => { console.log(` ID: ${row.id} | ${row.worker_name} | ${row.report_date}`); @@ -62,7 +58,6 @@ async function migrate() { AND dwr.work_type_id != ta.task_id `); - console.log(`\n✅ 업데이트 완료: ${updateResult.affectedRows}개 레코드 수정됨`); // 3. 수정 결과 확인 const [verifyResult] = await db.query(` @@ -80,7 +75,6 @@ async function migrate() { LIMIT 5 `); - console.log('\n📋 수정 후 샘플 확인:'); console.log('─'.repeat(80)); verifyResult.forEach(row => { console.log(` ID: ${row.id} | work_type_id: ${row.work_type_id} | task: ${row.task_name || 'N/A'} | 공정: ${row.work_type_name || 'N/A'}`); @@ -88,7 +82,7 @@ async function migrate() { console.log('─'.repeat(80)); } catch (error) { - console.error('❌ 마이그레이션 실패:', error.message); + console.error(' 마이그레이션 실패:', error.message); throw error; } } @@ -96,10 +90,9 @@ async function migrate() { // 실행 migrate() .then(() => { - console.log('\n🎉 마이그레이션 완료!'); process.exit(0); }) .catch(err => { - console.error('\n💥 마이그레이션 실패:', err); + console.error('\n 마이그레이션 실패:', err); process.exit(1); }); diff --git a/system1-factory/api/index.js b/system1-factory/api/index.js index 7e7661d..186d254 100644 --- a/system1-factory/api/index.js +++ b/system1-factory/api/index.js @@ -48,18 +48,14 @@ const server = app.listen(PORT, () => { env: process.env.NODE_ENV || 'development', nodeVersion: process.version }); - console.log(`\n🚀 서버가 포트 ${PORT}에서 실행 중입니다.`); - console.log(`📚 API 문서: http://localhost:${PORT}/api-docs\n`); }); // Graceful Shutdown const gracefulShutdown = (signal) => { logger.info(`${signal} 신호 수신 - 서버 종료 시작`); - console.log(`\n🛑 ${signal} 신호를 받았습니다. 서버를 종료합니다...`); server.close(async () => { logger.info('HTTP 서버 종료 완료'); - console.log('✅ HTTP 서버가 정상적으로 종료되었습니다.'); // 리소스 정리 try { @@ -79,7 +75,7 @@ const gracefulShutdown = (signal) => { // 30초 후 강제 종료 setTimeout(() => { logger.error('강제 종료 - 정상 종료 시간 초과'); - console.error('❌ 정상 종료 실패, 강제 종료합니다.'); + console.error(' 정상 종료 실패, 강제 종료합니다.'); process.exit(1); }, 30000); }; @@ -94,7 +90,7 @@ process.on('unhandledRejection', (reason, promise) => { reason: reason, promise: promise }); - console.error('⚠️ 처리되지 않은 Promise 거부:', reason); + console.error(' 처리되지 않은 Promise 거부:', reason); if (process.env.NODE_ENV === 'development') { process.exit(1); @@ -107,7 +103,7 @@ process.on('uncaughtException', (error) => { error: error.message, stack: error.stack }); - console.error('💥 처리되지 않은 예외:', error); + console.error(' 처리되지 않은 예외:', error); gracefulShutdown('UNCAUGHT_EXCEPTION'); }); @@ -117,11 +113,10 @@ process.on('uncaughtException', (error) => { if (cache.initRedis) { await cache.initRedis(); logger.info('캐시 시스템 초기화 완료'); - console.log('✅ 캐시 시스템 초기화 완료'); } } catch (error) { logger.warn('캐시 시스템 초기화 실패 - 계속 진행', { error: error.message }); - console.warn('⚠️ 캐시 시스템 초기화 실패:', error.message); + console.warn(' 캐시 시스템 초기화 실패:', error.message); } })(); diff --git a/system1-factory/api/models/monthlyStatusModel.js b/system1-factory/api/models/monthlyStatusModel.js index f40df94..0fea68e 100644 --- a/system1-factory/api/models/monthlyStatusModel.js +++ b/system1-factory/api/models/monthlyStatusModel.js @@ -109,7 +109,6 @@ class MonthlyStatusModel { updatedCount++; } - console.log(`✅ ${year}년 ${month}월 집계 재계산 완료: ${updatedCount}건`); return { success: true, updatedCount }; } catch (error) { diff --git a/system1-factory/api/models/tk_database.db b/system1-factory/api/models/tk_database.db deleted file mode 100644 index e69de29..0000000 diff --git a/system1-factory/api/models/visitRequestController.js b/system1-factory/api/models/visitRequestController.js deleted file mode 100644 index 948be3b..0000000 --- a/system1-factory/api/models/visitRequestController.js +++ /dev/null @@ -1,284 +0,0 @@ -const visitRequestModel = require('../models/visitRequestModel'); -const logger = require('../utils/logger'); - -// ==================== 출입 신청 관리 ==================== - -exports.createVisitRequest = async (req, res) => { - try { - const requester_id = req.user.user_id; - const requestData = { requester_id, ...req.body }; - - const requiredFields = ['visitor_company', 'category_id', 'workplace_id', 'visit_date', 'visit_time', 'purpose_id']; - for (const field of requiredFields) { - if (!requestData[field]) { - return res.status(400).json({ success: false, message: `${field}는 필수 입력 항목입니다.` }); - } - } - - const requestId = await visitRequestModel.createVisitRequest(requestData); - res.status(201).json({ - success: true, - message: '출입 신청이 성공적으로 생성되었습니다.', - data: { request_id: requestId } - }); - } catch (err) { - logger.error('출입 신청 생성 오류:', err); - res.status(500).json({ success: false, message: '출입 신청 생성 중 오류가 발생했습니다.' }); - } -}; - -exports.getAllVisitRequests = async (req, res) => { - try { - const filters = { - status: req.query.status, - visit_date: req.query.visit_date, - start_date: req.query.start_date, - end_date: req.query.end_date, - requester_id: req.query.requester_id, - category_id: req.query.category_id - }; - - const requests = await visitRequestModel.getAllVisitRequests(filters); - res.json({ success: true, data: requests }); - } catch (err) { - logger.error('출입 신청 목록 조회 오류:', err); - res.status(500).json({ success: false, message: '출입 신청 목록 조회 중 오류가 발생했습니다.' }); - } -}; - -exports.getVisitRequestById = async (req, res) => { - try { - const request = await visitRequestModel.getVisitRequestById(req.params.id); - if (!request) { - return res.status(404).json({ success: false, message: '출입 신청을 찾을 수 없습니다.' }); - } - res.json({ success: true, data: request }); - } catch (err) { - logger.error('출입 신청 조회 오류:', err); - res.status(500).json({ success: false, message: '출입 신청 조회 중 오류가 발생했습니다.' }); - } -}; - -exports.updateVisitRequest = async (req, res) => { - try { - const result = await visitRequestModel.updateVisitRequest(req.params.id, req.body); - if (result.affectedRows === 0) { - return res.status(404).json({ success: false, message: '출입 신청을 찾을 수 없습니다.' }); - } - res.json({ success: true, message: '출입 신청이 수정되었습니다.' }); - } catch (err) { - logger.error('출입 신청 수정 오류:', err); - res.status(500).json({ success: false, message: '출입 신청 수정 중 오류가 발생했습니다.' }); - } -}; - -exports.deleteVisitRequest = async (req, res) => { - try { - const result = await visitRequestModel.deleteVisitRequest(req.params.id); - if (result.affectedRows === 0) { - return res.status(404).json({ success: false, message: '출입 신청을 찾을 수 없습니다.' }); - } - res.json({ success: true, message: '출입 신청이 삭제되었습니다.' }); - } catch (err) { - logger.error('출입 신청 삭제 오류:', err); - res.status(500).json({ success: false, message: '출입 신청 삭제 중 오류가 발생했습니다.' }); - } -}; - -exports.approveVisitRequest = async (req, res) => { - try { - const result = await visitRequestModel.approveVisitRequest(req.params.id, req.user.user_id); - if (result.affectedRows === 0) { - return res.status(404).json({ success: false, message: '출입 신청을 찾을 수 없습니다.' }); - } - res.json({ success: true, message: '출입 신청이 승인되었습니다.' }); - } catch (err) { - logger.error('출입 신청 승인 오류:', err); - res.status(500).json({ success: false, message: '출입 신청 승인 중 오류가 발생했습니다.' }); - } -}; - -exports.rejectVisitRequest = async (req, res) => { - try { - const rejectionData = { - approved_by: req.user.user_id, - rejection_reason: req.body.rejection_reason || '사유 없음' - }; - const result = await visitRequestModel.rejectVisitRequest(req.params.id, rejectionData); - if (result.affectedRows === 0) { - return res.status(404).json({ success: false, message: '출입 신청을 찾을 수 없습니다.' }); - } - res.json({ success: true, message: '출입 신청이 반려되었습니다.' }); - } catch (err) { - logger.error('출입 신청 반려 오류:', err); - res.status(500).json({ success: false, message: '출입 신청 반려 중 오류가 발생했습니다.' }); - } -}; - -// ==================== 방문 목적 관리 ==================== - -exports.getAllVisitPurposes = async (req, res) => { - try { - const purposes = await visitRequestModel.getAllVisitPurposes(); - res.json({ success: true, data: purposes }); - } catch (err) { - logger.error('방문 목적 조회 오류:', err); - res.status(500).json({ success: false, message: '방문 목적 조회 중 오류가 발생했습니다.' }); - } -}; - -exports.getActiveVisitPurposes = async (req, res) => { - try { - const purposes = await visitRequestModel.getActiveVisitPurposes(); - res.json({ success: true, data: purposes }); - } catch (err) { - logger.error('활성 방문 목적 조회 오류:', err); - res.status(500).json({ success: false, message: '활성 방문 목적 조회 중 오류가 발생했습니다.' }); - } -}; - -exports.createVisitPurpose = async (req, res) => { - try { - if (!req.body.purpose_name) { - return res.status(400).json({ success: false, message: 'purpose_name은 필수 입력 항목입니다.' }); - } - const purposeId = await visitRequestModel.createVisitPurpose(req.body); - res.status(201).json({ - success: true, - message: '방문 목적이 추가되었습니다.', - data: { purpose_id: purposeId } - }); - } catch (err) { - logger.error('방문 목적 추가 오류:', err); - res.status(500).json({ success: false, message: '방문 목적 추가 중 오류가 발생했습니다.' }); - } -}; - -exports.updateVisitPurpose = async (req, res) => { - try { - const result = await visitRequestModel.updateVisitPurpose(req.params.id, req.body); - if (result.affectedRows === 0) { - return res.status(404).json({ success: false, message: '방문 목적을 찾을 수 없습니다.' }); - } - res.json({ success: true, message: '방문 목적이 수정되었습니다.' }); - } catch (err) { - logger.error('방문 목적 수정 오류:', err); - res.status(500).json({ success: false, message: '방문 목적 수정 중 오류가 발생했습니다.' }); - } -}; - -exports.deleteVisitPurpose = async (req, res) => { - try { - const result = await visitRequestModel.deleteVisitPurpose(req.params.id); - if (result.affectedRows === 0) { - return res.status(404).json({ success: false, message: '방문 목적을 찾을 수 없습니다.' }); - } - res.json({ success: true, message: '방문 목적이 삭제되었습니다.' }); - } catch (err) { - logger.error('방문 목적 삭제 오류:', err); - res.status(500).json({ success: false, message: '방문 목적 삭제 중 오류가 발생했습니다.' }); - } -}; - -// ==================== 안전교육 기록 관리 ==================== - -exports.createTrainingRecord = async (req, res) => { - try { - const trainingData = { trainer_id: req.user.user_id, ...req.body }; - - const requiredFields = ['request_id', 'training_date', 'training_start_time']; - for (const field of requiredFields) { - if (!trainingData[field]) { - return res.status(400).json({ success: false, message: `${field}는 필수 입력 항목입니다.` }); - } - } - - const trainingId = await visitRequestModel.createTrainingRecord(trainingData); - - // 안전교육 기록 생성 후 출입 신청 상태를 training_completed로 변경 - try { - await visitRequestModel.updateVisitRequestStatus(trainingData.request_id, 'training_completed'); - } catch (statusErr) { - logger.error('출입 신청 상태 업데이트 오류:', statusErr); - } - - res.status(201).json({ - success: true, - message: '안전교육 기록이 생성되었습니다.', - data: { training_id: trainingId } - }); - } catch (err) { - logger.error('안전교육 기록 생성 오류:', err); - res.status(500).json({ success: false, message: '안전교육 기록 생성 중 오류가 발생했습니다.' }); - } -}; - -exports.getTrainingRecordByRequestId = async (req, res) => { - try { - const record = await visitRequestModel.getTrainingRecordByRequestId(req.params.requestId); - res.json({ success: true, data: record || null }); - } catch (err) { - logger.error('안전교육 기록 조회 오류:', err); - res.status(500).json({ success: false, message: '안전교육 기록 조회 중 오류가 발생했습니다.' }); - } -}; - -exports.updateTrainingRecord = async (req, res) => { - try { - const result = await visitRequestModel.updateTrainingRecord(req.params.id, req.body); - if (result.affectedRows === 0) { - return res.status(404).json({ success: false, message: '안전교육 기록을 찾을 수 없습니다.' }); - } - res.json({ success: true, message: '안전교육 기록이 수정되었습니다.' }); - } catch (err) { - logger.error('안전교육 기록 수정 오류:', err); - res.status(500).json({ success: false, message: '안전교육 기록 수정 중 오류가 발생했습니다.' }); - } -}; - -exports.completeTraining = async (req, res) => { - try { - const trainingId = req.params.id; - const signatureData = req.body.signature_data; - - if (!signatureData) { - return res.status(400).json({ success: false, message: '서명 데이터가 필요합니다.' }); - } - - const result = await visitRequestModel.completeTraining(trainingId, signatureData); - if (result.affectedRows === 0) { - return res.status(404).json({ success: false, message: '안전교육 기록을 찾을 수 없습니다.' }); - } - - // 교육 완료 후 출입 신청 상태 변경 - try { - const record = await visitRequestModel.getTrainingRecordByRequestId(trainingId); - if (record) { - await visitRequestModel.updateVisitRequestStatus(record.request_id, 'training_completed'); - } - } catch (statusErr) { - logger.error('출입 신청 상태 업데이트 오류:', statusErr); - } - - res.json({ success: true, message: '안전교육이 완료되었습니다.' }); - } catch (err) { - logger.error('안전교육 완료 처리 오류:', err); - res.status(500).json({ success: false, message: '안전교육 완료 처리 중 오류가 발생했습니다.' }); - } -}; - -exports.getTrainingRecords = async (req, res) => { - try { - const filters = { - training_date: req.query.training_date, - start_date: req.query.start_date, - end_date: req.query.end_date, - trainer_id: req.query.trainer_id - }; - const records = await visitRequestModel.getTrainingRecords(filters); - res.json({ success: true, data: records }); - } catch (err) { - logger.error('안전교육 기록 목록 조회 오류:', err); - res.status(500).json({ success: false, message: '안전교육 기록 목록 조회 중 오류가 발생했습니다.' }); - } -}; diff --git a/system1-factory/api/models/workerModel.js b/system1-factory/api/models/workerModel.js index 9612825..4243b48 100644 --- a/system1-factory/api/models/workerModel.js +++ b/system1-factory/api/models/workerModel.js @@ -174,7 +174,6 @@ const remove = async (userId) => { try { await conn.beginTransaction(); - console.log(`🗑️ 작업자 삭제 시작: user_id=${userId}`); // 안전한 삭제: 각 테이블을 개별적으로 처리하고 오류가 발생해도 계속 진행 const tables = [ @@ -195,10 +194,8 @@ const remove = async (userId) => { try { const [result] = await conn.query(table.query, [userId]); if (result.affectedRows > 0) { - console.log(`✅ ${table.name} 테이블 ${table.action}: ${result.affectedRows}건`); } } catch (tableError) { - console.log(`⚠️ ${table.name} 테이블 ${table.action} 실패 (무시): ${tableError.message}`); } } @@ -207,14 +204,13 @@ const remove = async (userId) => { `DELETE FROM workers WHERE user_id = ?`, [userId] ); - console.log(`✅ 작업자 삭제 완료: ${result.affectedRows}건`); await conn.commit(); return result.affectedRows; } catch (err) { await conn.rollback(); - console.error(`❌ 작업자 삭제 오류 (user_id: ${userId}):`, err); + console.error(` 작업자 삭제 오류 (user_id: ${userId}):`, err); throw new Error(`작업자 삭제 중 오류가 발생했습니다: ${err.message}`); } finally { conn.release(); diff --git a/system1-factory/api/routes/authRoutes.js b/system1-factory/api/routes/authRoutes.js index 03c6a31..5650f9f 100644 --- a/system1-factory/api/routes/authRoutes.js +++ b/system1-factory/api/routes/authRoutes.js @@ -9,20 +9,12 @@ const express = require('express'); const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); -const mysql = require('mysql2/promise'); +const { getDb } = require('../dbPool'); const { verifyToken } = require('../middlewares/auth'); const { validatePassword, getPasswordError } = require('../utils/passwordValidator'); const router = express.Router(); const authController = require('../controllers/authController'); -// DB 연결 설정 -const dbConfig = { - host: process.env.DB_HOST || 'db_hyungi_net', - user: process.env.DB_USER || 'hyungi', - password: process.env.DB_PASSWORD, - database: process.env.DB_NAME || 'hyungi' -}; - // 로그인 시도 추적 (메모리 기반 - 실제로는 Redis 권장) const loginAttempts = new Map(); @@ -143,7 +135,7 @@ router.post('/refresh-token', async (req, res) => { return res.status(401).json({ error: '유효하지 않은 토큰입니다.' }); } - const connection = await mysql.createConnection(dbConfig); + const connection = await getDb(); // 사용자 정보 조회 const [users] = await connection.execute( @@ -151,8 +143,6 @@ router.post('/refresh-token', async (req, res) => { [decoded.user_id] ); - await connection.end(); - if (users.length === 0) { return res.status(404).json({ error: '사용자를 찾을 수 없습니다.' }); } @@ -167,7 +157,7 @@ router.post('/refresh-token', async (req, res) => { access_level: user.access_level, name: user.name || user.username }, - process.env.JWT_SECRET || 'your-secret-key', + process.env.JWT_SECRET, { expiresIn: process.env.JWT_EXPIRES_IN || '24h' } ); @@ -224,7 +214,7 @@ router.post('/change-password', verifyToken, async (req, res) => { }); } - connection = await mysql.createConnection(dbConfig); + connection = await getDb(); // 현재 사용자의 비밀번호 조회 const [users] = await connection.execute( @@ -290,10 +280,6 @@ router.post('/change-password', verifyToken, async (req, res) => { success: false, error: '서버 오류가 발생했습니다.' }); - } finally { - if (connection) { - await connection.end(); - } } }); @@ -334,7 +320,7 @@ router.post('/admin/change-password', verifyToken, async (req, res) => { }); } - connection = await mysql.createConnection(dbConfig); + connection = await getDb(); // 대상 사용자 확인 const [users] = await connection.execute( @@ -391,10 +377,6 @@ router.post('/admin/change-password', verifyToken, async (req, res) => { success: false, error: '서버 오류가 발생했습니다.' }); - } finally { - if (connection) { - await connection.end(); - } } }); @@ -453,7 +435,7 @@ router.get('/me', verifyToken, async (req, res) => { try { const userId = req.user.user_id; - connection = await mysql.createConnection(dbConfig); + connection = await getDb(); const [rows] = await connection.execute( 'SELECT user_id, username, name, email, access_level, last_login_at, created_at FROM users WHERE user_id = ?', [userId] @@ -477,10 +459,6 @@ router.get('/me', verifyToken, async (req, res) => { } catch (error) { console.error('Get current user error:', error); res.status(500).json({ error: '서버 오류가 발생했습니다.' }); - } finally { - if (connection) { - await connection.end(); - } } }); @@ -516,7 +494,7 @@ router.post('/register', verifyToken, async (req, res) => { }); } - connection = await mysql.createConnection(dbConfig); + connection = await getDb(); // 사용자명 중복 체크 const [existing] = await connection.execute( @@ -586,10 +564,6 @@ router.post('/register', verifyToken, async (req, res) => { success: false, error: '서버 오류가 발생했습니다.' }); - } finally { - if (connection) { - await connection.end(); - } } }); @@ -600,7 +574,7 @@ router.get('/users', verifyToken, async (req, res) => { let connection; try { - connection = await mysql.createConnection(dbConfig); + connection = await getDb(); // 기본 쿼리 (role 테이블과 JOIN) let query = ` @@ -656,10 +630,6 @@ router.get('/users', verifyToken, async (req, res) => { } catch (error) { console.error('Get users error:', error); res.status(500).json({ error: '서버 오류가 발생했습니다.' }); - } finally { - if (connection) { - await connection.end(); - } } }); @@ -691,7 +661,7 @@ router.put('/users/:id', verifyToken, async (req, res) => { } } - connection = await mysql.createConnection(dbConfig); + connection = await getDb(); // 사용자 존재 확인 const [existing] = await connection.execute( @@ -802,10 +772,6 @@ router.put('/users/:id', verifyToken, async (req, res) => { success: false, error: '서버 오류가 발생했습니다.' }); - } finally { - if (connection) { - await connection.end(); - } } }); @@ -834,7 +800,7 @@ router.delete('/users/:id', verifyToken, async (req, res) => { }); } - connection = await mysql.createConnection(dbConfig); + connection = await getDb(); // 사용자 존재 확인 const [existing] = await connection.execute( @@ -871,10 +837,6 @@ router.delete('/users/:id', verifyToken, async (req, res) => { success: false, error: '서버 오류가 발생했습니다.' }); - } finally { - if (connection) { - await connection.end(); - } } }); @@ -887,17 +849,13 @@ router.post('/logout', verifyToken, async (req, res) => { // 로그아웃 시간 기록 (선택사항) let connection; try { - connection = await mysql.createConnection(dbConfig); + connection = await getDb(); await connection.execute( 'UPDATE login_logs SET logout_time = NOW() WHERE user_id = ? AND logout_time IS NULL ORDER BY login_time DESC LIMIT 1', [req.user.user_id] ); } catch (error) { console.error('로그아웃 기록 실패:', error); - } finally { - if (connection) { - await connection.end(); - } } res.json({ @@ -916,7 +874,7 @@ router.get('/login-history', verifyToken, async (req, res) => { const { limit = 50, offset = 0 } = req.query; const userId = req.user.user_id; - connection = await mysql.createConnection(dbConfig); + connection = await getDb(); // 본인의 로그인 이력만 조회 (관리자는 전체 조회 가능) let query = ` @@ -958,10 +916,6 @@ router.get('/login-history', verifyToken, async (req, res) => { } catch (error) { console.error('Get login history error:', error); res.status(500).json({ error: '서버 오류가 발생했습니다.' }); - } finally { - if (connection) { - await connection.end(); - } } }); diff --git a/system1-factory/api/routes/dailyWorkReportRoutes.js b/system1-factory/api/routes/dailyWorkReportRoutes.js index 0dc64bc..a3ee51c 100644 --- a/system1-factory/api/routes/dailyWorkReportRoutes.js +++ b/system1-factory/api/routes/dailyWorkReportRoutes.js @@ -41,7 +41,6 @@ router.get('/check-overwrite', (req, res) => { }); } - console.log(`🔍 덮어쓰기 권한 확인: 날짜=${date}, 작업자=${user_id} (누적입력모드)`); // 누적입력 시스템에서는 항상 덮어쓰기 가능 (실제로는 누적만 함) res.json({ diff --git a/system1-factory/api/routes/setupRoutes.js b/system1-factory/api/routes/setupRoutes.js index b93200f..b89d9da 100644 --- a/system1-factory/api/routes/setupRoutes.js +++ b/system1-factory/api/routes/setupRoutes.js @@ -8,7 +8,6 @@ router.post('/setup-monthly-status', async (req, res) => { try { const db = await getDb(); - console.log('📊 월별 집계 테이블 생성 중...'); // 1. 월별 작업자 상태 집계 테이블 await db.execute(` @@ -86,7 +85,6 @@ router.post('/setup-monthly-status', async (req, res) => { ) COMMENT='월별 일자별 요약 테이블 (캘린더 최적화용)' `); - console.log('📊 집계 프로시저 생성 중...'); // 3. 집계 업데이트 프로시저 await db.execute(`DROP PROCEDURE IF EXISTS UpdateMonthlyWorkerStatus`); @@ -239,7 +237,6 @@ router.post('/setup-monthly-status', async (req, res) => { END `); - console.log('📊 트리거 생성 중...'); // 4. 트리거 생성 await db.execute(`DROP TRIGGER IF EXISTS tr_daily_work_reports_insert`); @@ -276,7 +273,6 @@ router.post('/setup-monthly-status', async (req, res) => { END `); - console.log('📊 기존 데이터로 집계 테이블 초기화 중...'); // 5. 기존 작업 데이터로 집계 테이블 초기화 const [existingDates] = await db.execute(` @@ -302,7 +298,6 @@ router.post('/setup-monthly-status', async (req, res) => { } if (i % 100 === 0) { - console.log(`📊 집계 초기화 진행률: ${processedCount}/${existingDates.length}`); } } @@ -329,7 +324,7 @@ router.post('/setup-monthly-status', async (req, res) => { }); } catch (error) { - console.error('❌ 월별 집계 시스템 설정 오류:', error); + console.error(' 월별 집계 시스템 설정 오류:', error); res.status(500).json({ success: false, message: '월별 집계 시스템 설정 중 오류가 발생했습니다.', @@ -340,12 +335,10 @@ router.post('/setup-monthly-status', async (req, res) => { router.post('/setup-attendance-db', async (req, res) => { try { - console.log('🚀 근태 관리 DB 설정 API 호출됨'); const db = await getDb(); // 1. 근로 유형 테이블 생성 - console.log('📋 근로 유형 테이블 생성 중...'); await db.execute(` CREATE TABLE IF NOT EXISTS work_attendance_types ( id INT PRIMARY KEY AUTO_INCREMENT, @@ -359,7 +352,6 @@ router.post('/setup-attendance-db', async (req, res) => { `); // 2. 휴가 유형 테이블 생성 - console.log('🏖️ 휴가 유형 테이블 생성 중...'); await db.execute(` CREATE TABLE IF NOT EXISTS vacation_types ( id INT PRIMARY KEY AUTO_INCREMENT, @@ -374,7 +366,6 @@ router.post('/setup-attendance-db', async (req, res) => { `); // 3. 일일 근태 기록 테이블 생성 - console.log('📊 일일 근태 기록 테이블 생성 중...'); await db.execute(` CREATE TABLE IF NOT EXISTS daily_attendance_records ( id INT PRIMARY KEY AUTO_INCREMENT, @@ -394,7 +385,6 @@ router.post('/setup-attendance-db', async (req, res) => { `); // 4. 작업자별 휴가 잔여 관리 테이블 생성 - console.log('👥 작업자별 휴가 잔여 관리 테이블 생성 중...'); await db.execute(` CREATE TABLE IF NOT EXISTS worker_vacation_balance ( id INT PRIMARY KEY AUTO_INCREMENT, @@ -410,7 +400,6 @@ router.post('/setup-attendance-db', async (req, res) => { `); // 5. 기본 데이터 삽입 - console.log('📝 기본 데이터 삽입 중...'); // 근로 유형 기본 데이터 await db.execute(` @@ -446,7 +435,7 @@ router.post('/setup-attendance-db', async (req, res) => { }); } catch (error) { - console.error('❌ DB 설정 API 오류:', error); + console.error(' DB 설정 API 오류:', error); res.status(500).json({ success: false, message: 'DB 설정 중 오류가 발생했습니다.', @@ -460,7 +449,6 @@ router.post('/add-overtime-warning', async (req, res) => { try { const db = await getDb(); - console.log('⚠️ 12시간 초과 상태 컬럼 추가 중...'); // 1. monthly_summary 테이블에 컬럼 추가 try { @@ -468,10 +456,8 @@ router.post('/add-overtime-warning', async (req, res) => { ALTER TABLE monthly_summary ADD COLUMN overtime_warning_workers INT DEFAULT 0 COMMENT '확인필요(12시간초과) 작업자 수' AFTER error_workers `); - console.log('✅ overtime_warning_workers 컬럼 추가 완료'); } catch (error) { if (error.code === 'ER_DUP_FIELDNAME') { - console.log('ℹ️ overtime_warning_workers 컬럼이 이미 존재합니다.'); } else { throw error; } @@ -482,10 +468,8 @@ router.post('/add-overtime-warning', async (req, res) => { ALTER TABLE monthly_summary ADD COLUMN has_overtime_warning BOOLEAN DEFAULT FALSE COMMENT '확인필요 상태 있음' AFTER has_errors `); - console.log('✅ has_overtime_warning 컬럼 추가 완료'); } catch (error) { if (error.code === 'ER_DUP_FIELDNAME') { - console.log('ℹ️ has_overtime_warning 컬럼이 이미 존재합니다.'); } else { throw error; } @@ -551,7 +535,6 @@ router.post('/add-overtime-warning', async (req, res) => { last_updated = CURRENT_TIMESTAMP; END `); - console.log('✅ UpdateDailySummary 프로시저 업데이트 완료'); res.json({ success: true, @@ -561,7 +544,7 @@ router.post('/add-overtime-warning', async (req, res) => { }); } catch (error) { - console.error('❌ 12시간 초과 상태 설정 오류:', error); + console.error(' 12시간 초과 상태 설정 오류:', error); res.status(500).json({ success: false, message: '12시간 초과 상태 설정 실패', @@ -575,7 +558,6 @@ router.post('/migrate-existing-data', async (req, res) => { try { const db = await getDb(); - console.log('🔄 기존 데이터 마이그레이션 시작...'); // 1. 기존 데이터 범위 확인 const [dateRange] = await db.execute(` @@ -595,12 +577,10 @@ router.post('/migrate-existing-data', async (req, res) => { } const { min_date, max_date, total_reports } = dateRange[0]; - console.log(`📊 데이터 범위: ${min_date} ~ ${max_date} (총 ${total_reports}건)`); // 2. 기존 monthly_worker_status, monthly_summary 데이터 삭제 await db.execute('DELETE FROM monthly_summary'); await db.execute('DELETE FROM monthly_worker_status'); - console.log('🗑️ 기존 집계 데이터 삭제 완료'); // 3. 날짜별로 작업자별 상태 재계산 const [allDates] = await db.execute(` @@ -610,7 +590,6 @@ router.post('/migrate-existing-data', async (req, res) => { ORDER BY report_date, worker_id `, [min_date, max_date]); - console.log(`🔄 ${allDates.length}개 날짜-작업자 조합 처리 중...`); let processedCount = 0; for (const { report_date, worker_id } of allDates) { @@ -620,10 +599,9 @@ router.post('/migrate-existing-data', async (req, res) => { processedCount++; if (processedCount % 50 === 0) { - console.log(`📈 진행률: ${processedCount}/${allDates.length} (${Math.round(processedCount/allDates.length*100)}%)`); } } catch (error) { - console.error(`❌ ${report_date} ${worker_id} 처리 오류:`, error.message); + console.error(` ${report_date} ${worker_id} 처리 오류:`, error.message); } } @@ -631,7 +609,6 @@ router.post('/migrate-existing-data', async (req, res) => { const [workerStatusCount] = await db.execute('SELECT COUNT(*) as count FROM monthly_worker_status'); const [summaryCount] = await db.execute('SELECT COUNT(*) as count FROM monthly_summary'); - console.log(`✅ 마이그레이션 완료:`); console.log(` - monthly_worker_status: ${workerStatusCount[0].count}건`); console.log(` - monthly_summary: ${summaryCount[0].count}건`); @@ -649,7 +626,7 @@ router.post('/migrate-existing-data', async (req, res) => { }); } catch (error) { - console.error('❌ 데이터 마이그레이션 오류:', error); + console.error(' 데이터 마이그레이션 오류:', error); res.status(500).json({ success: false, message: '데이터 마이그레이션 실패', @@ -705,7 +682,7 @@ router.get('/check-data-status', async (req, res) => { }); } catch (error) { - console.error('❌ DB 상태 확인 오류:', error); + console.error(' DB 상태 확인 오류:', error); res.status(500).json({ success: false, message: 'DB 상태 확인 실패', diff --git a/system1-factory/api/routes/systemRoutes.js b/system1-factory/api/routes/systemRoutes.js index 6e8306d..e2d5243 100644 --- a/system1-factory/api/routes/systemRoutes.js +++ b/system1-factory/api/routes/systemRoutes.js @@ -2,6 +2,7 @@ const express = require('express'); const router = express.Router(); const systemController = require('../controllers/systemController'); +const userController = require('../controllers/userController'); const { requireAuth, requireRole } = require('../middlewares/auth'); // 모든 라우트에 인증 및 시스템 권한 확인 적용 @@ -46,31 +47,31 @@ router.get('/users/stats', systemController.getUserStats); * GET /api/system/users * 모든 사용자 목록 조회 */ -router.get('/users', systemController.getAllUsers); +router.get('/users', userController.getAllUsers); /** * POST /api/system/users * 새 사용자 생성 */ -router.post('/users', systemController.createUser); +router.post('/users', userController.createUser); /** * PUT /api/system/users/:id * 사용자 정보 수정 */ -router.put('/users/:id', systemController.updateUser); +router.put('/users/:id', userController.updateUser); /** * DELETE /api/system/users/:id * 사용자 삭제 */ -router.delete('/users/:id', systemController.deleteUser); +router.delete('/users/:id', userController.deleteUser); /** * POST /api/system/users/:id/reset-password * 사용자 비밀번호 재설정 */ -router.post('/users/:id/reset-password', systemController.resetUserPassword); +router.post('/users/:id/reset-password', userController.resetUserPassword); // ===== 시스템 로그 관련 ===== @@ -219,7 +220,6 @@ router.post('/migrations/fix-work-type-id', async (req, res) => { const { getDb } = require('../dbPool'); const db = await getDb(); - console.log('🔄 TBM 기반 작업보고서 work_type_id 수정 시작...'); // 1. 수정 대상 확인 const [checkResult] = await db.query(` @@ -256,7 +256,6 @@ router.post('/migrations/fix-work-type-id', async (req, res) => { AND dwr.work_type_id != ta.task_id `); - console.log(`✅ 업데이트 완료: ${updateResult.affectedRows}개 레코드 수정됨`); // 3. 수정된 샘플 조회 const [samples] = await db.query(` diff --git a/system1-factory/api/services/auth.service.js b/system1-factory/api/services/auth.service.js index ce013b6..0b98761 100644 --- a/system1-factory/api/services/auth.service.js +++ b/system1-factory/api/services/auth.service.js @@ -56,15 +56,19 @@ const loginService = async (username, password, ipAddress, userAgent) => { await userModel.resetLoginAttempts(user.user_id); + if (!process.env.JWT_SECRET) { + throw new Error('JWT_SECRET 환경변수가 설정되지 않았습니다'); + } + const token = jwt.sign( { user_id: user.user_id, username: user.username, role: user.role_name, role_id: user.role_id, access_level: user.access_level, name: user.name || user.username }, - process.env.JWT_SECRET || 'your-secret-key', + process.env.JWT_SECRET, { expiresIn: process.env.JWT_EXPIRES_IN || '24h' } ); const refreshToken = jwt.sign( { user_id: user.user_id, type: 'refresh' }, - process.env.JWT_REFRESH_SECRET || process.env.JWT_SECRET || 'your-refresh-secret', + process.env.JWT_REFRESH_SECRET || process.env.JWT_SECRET, { expiresIn: process.env.JWT_REFRESH_EXPIRES_IN || '7d' } ); diff --git a/system1-factory/api/setup-attendance-db.js b/system1-factory/api/setup-attendance-db.js index 284124a..0cfc817 100644 --- a/system1-factory/api/setup-attendance-db.js +++ b/system1-factory/api/setup-attendance-db.js @@ -4,12 +4,10 @@ const path = require('path'); async function setupAttendanceDB() { try { - console.log('🚀 근태 관리 DB 설정 시작...'); const db = await getDb(); // 1. 근로 유형 테이블 생성 - console.log('📋 근로 유형 테이블 생성 중...'); await db.execute(` CREATE TABLE IF NOT EXISTS work_attendance_types ( id INT PRIMARY KEY AUTO_INCREMENT, @@ -23,7 +21,6 @@ async function setupAttendanceDB() { `); // 2. 휴가 유형 테이블 생성 - console.log('🏖️ 휴가 유형 테이블 생성 중...'); await db.execute(` CREATE TABLE IF NOT EXISTS vacation_types ( id INT PRIMARY KEY AUTO_INCREMENT, @@ -38,7 +35,6 @@ async function setupAttendanceDB() { `); // 3. 일일 근태 기록 테이블 생성 - console.log('📊 일일 근태 기록 테이블 생성 중...'); await db.execute(` CREATE TABLE IF NOT EXISTS daily_attendance_records ( id INT PRIMARY KEY AUTO_INCREMENT, @@ -66,7 +62,6 @@ async function setupAttendanceDB() { `); // 4. 작업자 휴가 잔여 관리 테이블 생성 - console.log('📅 휴가 잔여 관리 테이블 생성 중...'); await db.execute(` CREATE TABLE IF NOT EXISTS worker_vacation_balance ( id INT PRIMARY KEY AUTO_INCREMENT, @@ -85,7 +80,6 @@ async function setupAttendanceDB() { `); // 5. 기본 데이터 삽입 - console.log('📝 기본 데이터 삽입 중...'); // 근로 유형 기본 데이터 await db.execute(` @@ -109,7 +103,6 @@ async function setupAttendanceDB() { `); // 6. 휴가 전용 작업 유형 추가 - console.log('🏖️ 휴가 전용 작업 유형 추가 중...'); await db.execute(` INSERT IGNORE INTO work_types (name, description, is_active) VALUES ('휴가', '연차, 반차, 병가 등 휴가 처리용', TRUE) @@ -121,42 +114,32 @@ async function setupAttendanceDB() { ALTER TABLE daily_work_reports ADD COLUMN attendance_record_id INT NULL COMMENT '근태 기록 ID' AFTER updated_by `); - console.log('✅ daily_work_reports 테이블에 attendance_record_id 컬럼 추가됨'); } catch (error) { if (error.code !== 'ER_DUP_FIELDNAME') { - console.log('⚠️ attendance_record_id 컬럼 추가 실패 (이미 존재할 수 있음):', error.message); } else { - console.log('✅ attendance_record_id 컬럼이 이미 존재함'); } } // 8. 인덱스 추가 try { await db.execute(`CREATE INDEX idx_attendance_record ON daily_work_reports(attendance_record_id)`); - console.log('✅ attendance_record_id 인덱스 추가됨'); } catch (error) { - console.log('⚠️ 인덱스 추가 실패 (이미 존재할 수 있음):', error.message); } try { await db.execute(`CREATE INDEX idx_daily_work_reports_worker_date ON daily_work_reports(worker_id, report_date)`); - console.log('✅ worker_date 인덱스 추가됨'); } catch (error) { - console.log('⚠️ 인덱스 추가 실패 (이미 존재할 수 있음):', error.message); } - console.log('🎉 근태 관리 DB 설정 완료!'); console.log(''); - console.log('📋 생성된 테이블:'); console.log(' - work_attendance_types (근로 유형)'); console.log(' - vacation_types (휴가 유형)'); console.log(' - daily_attendance_records (일일 근태 기록)'); console.log(' - worker_vacation_balance (휴가 잔여 관리)'); console.log(''); - console.log('✅ 기본 데이터도 모두 삽입되었습니다.'); } catch (error) { - console.error('❌ DB 설정 중 오류 발생:', error); + console.error(' DB 설정 중 오류 발생:', error); throw error; } } @@ -165,11 +148,10 @@ async function setupAttendanceDB() { if (require.main === module) { setupAttendanceDB() .then(() => { - console.log('✅ 설정 완료'); process.exit(0); }) .catch((error) => { - console.error('❌ 설정 실패:', error); + console.error(' 설정 실패:', error); process.exit(1); }); } diff --git a/system1-factory/api/utils/cache.js b/system1-factory/api/utils/cache.js index de2002f..4421a53 100644 --- a/system1-factory/api/utils/cache.js +++ b/system1-factory/api/utils/cache.js @@ -41,7 +41,6 @@ const initRedis = async () => { }); redisClient.on('connect', () => { - console.log('✅ Redis 캐시 연결 성공'); }); await redisClient.connect(); @@ -200,7 +199,6 @@ const createCacheMiddleware = (keyGenerator, ttl = TTL.MEDIUM) => { const cachedData = await get(cacheKey); if (cachedData) { - console.log(`🎯 캐시 히트: ${cacheKey}`); return res.json(cachedData); } @@ -212,7 +210,6 @@ const createCacheMiddleware = (keyGenerator, ttl = TTL.MEDIUM) => { // 성공 응답만 캐시 if (res.statusCode >= 200 && res.statusCode < 300) { set(cacheKey, data, ttl).then(() => { - console.log(`💾 캐시 저장: ${cacheKey}`); }); }