refactor: System1 API 인증 체계 SSO 전환 및 마이그레이션 정비

- SSO JWT 인증으로 전환 (auth.service.js)
- worker_id → user_id 마이그레이션 완료
- departments 연동, CORS 미들웨어 정리
- 불필요 파일 삭제 (tk_database.db, visitRequestController.js)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-03-06 23:18:00 +09:00
parent 2f7e083db0
commit ec755ed52f
47 changed files with 181 additions and 716 deletions

View File

@@ -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 컬럼 삭제 완료');
};

View File

@@ -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 테이블 컬럼 제거 완료');
};

View File

@@ -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('✅ 모든 출근/근태 관련 테이블 제거 완료');
};

View File

@@ -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 테이블을 관리하세요.');
};

View File

@@ -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('✅ 게스트 역할 제거 완료');
};

View File

@@ -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 시스템 테이블 제거 완료');
};

View File

@@ -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 페이지 제거 완료');
};

View File

@@ -24,7 +24,6 @@ exports.up = function(knex) {
table.index('work_type_id');
table.index('is_active');
}).then(() => {
console.log('✅ tasks 테이블 생성 완료');
});
};

View File

@@ -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 컬럼 추가 완료');
});
};

View File

@@ -143,10 +143,8 @@ exports.up = async function(knex) {
}
]);
console.log('✅ 현재 사용 중인 페이지 목록 업데이트 완료');
};
exports.down = async function(knex) {
await knex('pages').del();
console.log('✅ 페이지 목록 삭제 완료');
};

View File

@@ -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 컬럼 제거 완료');
};

View File

@@ -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 테이블 삭제 완료');
};

View File

@@ -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 테이블 삭제 완료');
};

View File

@@ -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('✅ 출퇴근 관리 페이지 삭제 완료');
};

View File

@@ -18,9 +18,7 @@ exports.up = async function(knex) {
.whereNotNull('id')
.update({ is_present: true });
console.log('✅ is_present 컬럼 추가 완료');
} else {
console.log('⏭️ is_present 컬럼이 이미 존재합니다');
}
};

View File

@@ -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('✅ 휴가 관리 페이지 롤백 완료');
};

View File

@@ -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 테이블 롤백 완료');
};

View File

@@ -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 테이블 롤백 완료');
};

View File

@@ -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('✅ 휴가 관리 페이지 롤백 완료');
};

View File

@@ -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('✅ 출입 신청 및 안전교육 시스템 테이블 삭제 완료');
};

View File

@@ -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('✅ 출입 신청 및 안전관리 페이지 삭제 완료');
};

View File

@@ -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('✅ 문제 신고 페이지 삭제 완료');
};

View File

@@ -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 컬럼 삭제 완료');
};

View File

@@ -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('✅ 모든 일일순회점검 시스템 테이블 제거 완료');
};

View File

@@ -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(' 데이터 복구가 필요한 경우 백업에서 복원해주세요.');
};

View File

@@ -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 컬럼이 이미 존재합니다');
}
};

View File

@@ -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 제거`);
}
}
}