Files
tk-factory-services/user-management/api/models/permissionModel.js
Hyungi Ahn 5b1b89254c feat: RAG 임베딩 자동 동기화 + AI 서비스 개선
- 부적합 라이프사이클 전 과정에서 Qdrant 임베딩 자동 동기화
  - 관리함 5개 저장 함수 + 수신함 상태 변경 시 fire-and-forget sync
  - 30분 주기 전체 재동기화 안전망 (FastAPI lifespan 백그라운드 태스크)
  - build_document_text에 카테고리(final_category/category) 포함
- RAG 질의에 DB 통계 집계 지원 (카테고리별/부서별 건수)
- Qdrant client.search → query_points API 마이그레이션
- AI 어시스턴트 페이지 권한 추가 (tkuser)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 13:05:32 +09:00

275 lines
11 KiB
JavaScript

/**
* Permission Model
*
* MariaDB user_page_permissions 테이블 CRUD
*/
const { getPool } = require('./userModel');
// 기본 페이지 목록 (시스템별 구분)
const DEFAULT_PAGES = {
// ===== System 1 - 공장관리 =====
// 작업 관리
's1.dashboard': { title: '대시보드', system: 'system1', group: '작업 관리', default_access: true },
's1.work.tbm': { title: 'TBM 관리', system: 'system1', group: '작업 관리', default_access: true },
's1.work.report_create': { title: '작업보고서 작성', system: 'system1', group: '작업 관리', default_access: true },
's1.work.analysis': { title: '작업 분석', system: 'system1', group: '작업 관리', default_access: false },
's1.work.nonconformity': { title: '부적합 현황', system: 'system1', group: '작업 관리', default_access: true },
// 공장 관리
's1.factory.repair_management':{ title: '시설설비 관리', system: 'system1', group: '공장 관리', default_access: false },
's1.inspection.daily_patrol': { title: '일일순회점검', system: 'system1', group: '공장 관리', default_access: false },
's1.inspection.checkin': { title: '출근 체크', system: 'system1', group: '공장 관리', default_access: true },
's1.inspection.work_status': { title: '근무 현황', system: 'system1', group: '공장 관리', default_access: false },
// 안전 관리
's1.safety.visit_request': { title: '출입 신청', system: 'system1', group: '안전 관리', default_access: true },
's1.safety.management': { title: '안전 관리', system: 'system1', group: '안전 관리', default_access: false },
's1.safety.checklist_manage': { title: '체크리스트 관리', system: 'system1', group: '안전 관리', default_access: false },
// 근태 관리
's1.attendance.my_vacation_info': { title: '내 연차 정보', system: 'system1', group: '근태 관리', default_access: true },
's1.attendance.monthly': { title: '월간 근태', system: 'system1', group: '근태 관리', default_access: true },
's1.attendance.vacation_request': { title: '휴가 신청', system: 'system1', group: '근태 관리', default_access: true },
's1.attendance.vacation_management': { title: '휴가 관리', system: 'system1', group: '근태 관리', default_access: false },
's1.attendance.vacation_allocation': { title: '휴가 발생 입력', system: 'system1', group: '근태 관리', default_access: false },
's1.attendance.annual_overview': { title: '연간 휴가 현황', system: 'system1', group: '근태 관리', default_access: false },
// 시스템 관리
's1.admin.workers': { title: '작업자 관리', system: 'system1', group: '시스템 관리', default_access: false },
's1.admin.projects': { title: '프로젝트 관리', system: 'system1', group: '시스템 관리', default_access: false },
's1.admin.tasks': { title: '작업 관리', system: 'system1', group: '시스템 관리', default_access: false },
's1.admin.workplaces': { title: '작업장 관리', system: 'system1', group: '시스템 관리', default_access: false },
's1.admin.equipments': { title: '설비 관리', system: 'system1', group: '시스템 관리', default_access: false },
's1.admin.issue_categories': { title: '신고 카테고리 관리', system: 'system1', group: '시스템 관리', default_access: false },
's1.admin.attendance_report': { title: '출퇴근-보고서 대조', system: 'system1', group: '시스템 관리', default_access: false },
// ===== System 3 - 부적합관리 =====
// 메인
'issues_dashboard': { title: '현황판', system: 'system3', group: '메인', default_access: true },
'issues_inbox': { title: '수신함', system: 'system3', group: '메인', default_access: true },
'issues_management': { title: '관리함', system: 'system3', group: '메인', default_access: false },
'issues_archive': { title: '폐기함', system: 'system3', group: '메인', default_access: false },
// 보고서
'reports': { title: '보고서', system: 'system3', group: '보고서', default_access: false },
'reports_daily': { title: '일일보고서', system: 'system3', group: '보고서', default_access: false },
'reports_weekly': { title: '주간보고서', system: 'system3', group: '보고서', default_access: false },
'reports_monthly': { title: '월간보고서', system: 'system3', group: '보고서', default_access: false },
// AI
'ai_assistant': { title: 'AI 어시스턴트', system: 'system3', group: 'AI', default_access: false }
};
/**
* 사용자의 페이지 권한 목록 조회
*/
async function getUserPermissions(userId) {
const db = getPool();
const [rows] = await db.query(
'SELECT * FROM user_page_permissions WHERE user_id = ? ORDER BY page_name',
[userId]
);
return rows;
}
/**
* 단건 권한 부여/업데이트 (UPSERT)
*/
async function grantPermission({ user_id, page_name, can_access, granted_by_id, notes }) {
const db = getPool();
const [result] = await db.query(
`INSERT INTO user_page_permissions (user_id, page_name, can_access, granted_by_id, notes)
VALUES (?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE can_access = VALUES(can_access), granted_by_id = VALUES(granted_by_id), notes = VALUES(notes), granted_at = CURRENT_TIMESTAMP`,
[user_id, page_name, can_access, granted_by_id, notes || null]
);
return { id: result.insertId, user_id, page_name, can_access };
}
/**
* 일괄 권한 부여
*/
async function bulkGrant({ user_id, permissions, granted_by_id }) {
const db = getPool();
let count = 0;
for (const perm of permissions) {
if (!DEFAULT_PAGES[perm.page_name]) continue;
await db.query(
`INSERT INTO user_page_permissions (user_id, page_name, can_access, granted_by_id)
VALUES (?, ?, ?, ?)
ON DUPLICATE KEY UPDATE can_access = VALUES(can_access), granted_by_id = VALUES(granted_by_id), granted_at = CURRENT_TIMESTAMP`,
[user_id, perm.page_name, perm.can_access, granted_by_id]
);
count++;
}
return { updated_count: count };
}
/**
* 접근 권한 확인 (우선순위: 개인 > 부서 > 기본값)
*/
async function checkAccess(userId, pageName) {
const db = getPool();
// 1. 명시적 개인 권한
const [rows] = await db.query(
'SELECT can_access FROM user_page_permissions WHERE user_id = ? AND page_name = ?',
[userId, pageName]
);
if (rows.length > 0) {
return { can_access: rows[0].can_access, reason: 'explicit_permission' };
}
// 2. 부서 권한
const [userRows] = await db.query(
'SELECT department_id FROM sso_users WHERE user_id = ?',
[userId]
);
if (userRows.length > 0 && userRows[0].department_id) {
const [deptRows] = await db.query(
'SELECT can_access FROM department_page_permissions WHERE department_id = ? AND page_name = ?',
[userRows[0].department_id, pageName]
);
if (deptRows.length > 0) {
return { can_access: deptRows[0].can_access, reason: 'department_permission' };
}
}
// 3. 기본 권한
const pageConfig = DEFAULT_PAGES[pageName];
if (!pageConfig) {
return { can_access: false, reason: 'invalid_page' };
}
return { can_access: pageConfig.default_access, reason: 'default_permission' };
}
/**
* 부서별 페이지 권한 조회
*/
async function getDepartmentPermissions(departmentId) {
const db = getPool();
const [rows] = await db.query(
'SELECT * FROM department_page_permissions WHERE department_id = ? ORDER BY page_name',
[departmentId]
);
return rows;
}
/**
* 부서 권한 단건 UPSERT
*/
async function setDepartmentPermission({ department_id, page_name, can_access, granted_by_id }) {
const db = getPool();
const [result] = await db.query(
`INSERT INTO department_page_permissions (department_id, page_name, can_access, granted_by_id)
VALUES (?, ?, ?, ?)
ON DUPLICATE KEY UPDATE can_access = VALUES(can_access), granted_by_id = VALUES(granted_by_id), granted_at = CURRENT_TIMESTAMP`,
[department_id, page_name, can_access, granted_by_id]
);
return { id: result.insertId, department_id, page_name, can_access };
}
/**
* 부서 권한 일괄 설정
*/
async function bulkSetDepartmentPermissions({ department_id, permissions, granted_by_id }) {
const db = getPool();
let count = 0;
for (const perm of permissions) {
if (!DEFAULT_PAGES[perm.page_name]) continue;
await db.query(
`INSERT INTO department_page_permissions (department_id, page_name, can_access, granted_by_id)
VALUES (?, ?, ?, ?)
ON DUPLICATE KEY UPDATE can_access = VALUES(can_access), granted_by_id = VALUES(granted_by_id), granted_at = CURRENT_TIMESTAMP`,
[department_id, perm.page_name, perm.can_access, granted_by_id]
);
count++;
}
return { updated_count: count };
}
/**
* 부서 권한 삭제 (기본값으로 복귀)
*/
async function deleteDepartmentPermission(departmentId, pageName) {
const db = getPool();
const [result] = await db.query(
'DELETE FROM department_page_permissions WHERE department_id = ? AND page_name = ?',
[departmentId, pageName]
);
return result.affectedRows > 0;
}
/**
* 사용자 권한 + 출처 조회 (각 페이지별 현재 적용 권한과 출처 반환)
*/
async function getUserPermissionsWithSource(userId) {
const db = getPool();
// 개인 권한 조회
const [userPerms] = await db.query(
'SELECT page_name, can_access FROM user_page_permissions WHERE user_id = ?',
[userId]
);
const userPermMap = {};
userPerms.forEach(p => { userPermMap[p.page_name] = !!p.can_access; });
// 부서 권한 조회
const [userRows] = await db.query(
'SELECT department_id FROM sso_users WHERE user_id = ?',
[userId]
);
const deptId = userRows.length > 0 ? userRows[0].department_id : null;
const deptPermMap = {};
if (deptId) {
const [deptPerms] = await db.query(
'SELECT page_name, can_access FROM department_page_permissions WHERE department_id = ?',
[deptId]
);
deptPerms.forEach(p => { deptPermMap[p.page_name] = !!p.can_access; });
}
// 모든 페이지에 대해 결과 조합
const result = {};
for (const [pageName, config] of Object.entries(DEFAULT_PAGES)) {
if (pageName in userPermMap) {
result[pageName] = { can_access: userPermMap[pageName], source: 'explicit' };
} else if (pageName in deptPermMap) {
result[pageName] = { can_access: deptPermMap[pageName], source: 'department' };
} else {
result[pageName] = { can_access: config.default_access, source: 'default' };
}
}
return { permissions: result, department_id: deptId };
}
/**
* 권한 삭제 (기본값으로 되돌림)
*/
async function deletePermission(permissionId) {
const db = getPool();
const [rows] = await db.query(
'SELECT * FROM user_page_permissions WHERE id = ?',
[permissionId]
);
if (rows.length === 0) return null;
await db.query('DELETE FROM user_page_permissions WHERE id = ?', [permissionId]);
return rows[0];
}
module.exports = {
DEFAULT_PAGES,
getUserPermissions,
grantPermission,
bulkGrant,
checkAccess,
deletePermission,
getDepartmentPermissions,
setDepartmentPermission,
bulkSetDepartmentPermissions,
deleteDepartmentPermission,
getUserPermissionsWithSource
};