feat: 초기 프로젝트 설정 및 룰.md 파일 추가

This commit is contained in:
2025-07-28 09:53:31 +09:00
commit 09a4d38512
8165 changed files with 1021855 additions and 0 deletions

View File

@@ -0,0 +1,210 @@
// routes/auth.js
const express = require('express');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const { verifyToken } = require('../middlewares/authMiddleware');
const { requireAccess } = require('../utils/access');
const router = express.Router();
// 임시 사용자 데이터
let users = [
{
user_id: 1,
username: 'admin',
password: '$2b$10$example',
name: '관리자',
access_level: 'admin',
worker_id: null,
created_at: new Date()
},
{
user_id: 2,
username: 'group_leader1',
password: '$2b$10$example',
name: '김그룹장',
access_level: 'group_leader',
worker_id: 1,
created_at: new Date()
}
];
/**
* 로그인
*/
router.post('/login', async (req, res) => {
try {
const { username, password } = req.body;
if (!username || !password) {
return res.status(400).json({ error: '사용자명과 비밀번호를 입력해주세요.' });
}
const user = users.find(u => u.username === username);
if (!user) {
return res.status(401).json({ error: '사용자를 찾을 수 없습니다.' });
}
// 비밀번호 확인 (실제로는 bcrypt.compare 사용)
const isValid = password === 'password'; // 임시
if (!isValid) {
return res.status(401).json({ error: '비밀번호가 올바르지 않습니다.' });
}
// JWT 토큰 생성
const token = jwt.sign(
{
user_id: user.user_id,
username: user.username,
access_level: user.access_level,
worker_id: user.worker_id
},
process.env.JWT_SECRET || 'your-secret-key',
{ expiresIn: '24h' }
);
res.json({
success: true,
token,
user: {
user_id: user.user_id,
username: user.username,
name: user.name,
access_level: user.access_level,
worker_id: user.worker_id
}
});
} catch (error) {
console.error('Login error:', error);
res.status(500).json({ error: '서버 오류가 발생했습니다.' });
}
});
/**
* 현재 사용자 정보 조회
*/
router.get('/me', verifyToken, (req, res) => {
try {
const userId = req.user.user_id;
const user = users.find(u => u.user_id === userId);
if (!user) {
return res.status(404).json({ error: '사용자를 찾을 수 없습니다.' });
}
res.json({
user_id: user.user_id,
username: user.username,
name: user.name,
access_level: user.access_level,
worker_id: user.worker_id
});
} catch (error) {
console.error('Get current user error:', error);
res.status(500).json({ error: '서버 오류가 발생했습니다.' });
}
});
/**
* 사용자 등록 (관리자만)
*/
router.post('/register', verifyToken, requireAccess('admin'), async (req, res) => {
try {
const { username, password, name, access_level, worker_id } = req.body;
if (!username || !password || !name || !access_level) {
return res.status(400).json({ error: '필수 항목을 모두 입력해주세요.' });
}
// 사용자명 중복 체크
const existingUser = users.find(u => u.username === username);
if (existingUser) {
return res.status(409).json({ error: '이미 존재하는 사용자명입니다.' });
}
// 비밀번호 해시
const hashedPassword = await bcrypt.hash(password, 10);
const newUser = {
user_id: users.length + 1,
username,
password: hashedPassword,
name,
access_level,
worker_id: worker_id || null,
created_at: new Date()
};
users.push(newUser);
res.json({
success: true,
message: '사용자가 성공적으로 등록되었습니다.',
user: {
user_id: newUser.user_id,
username: newUser.username,
name: newUser.name,
access_level: newUser.access_level,
worker_id: newUser.worker_id
}
});
} catch (error) {
console.error('Register error:', error);
res.status(500).json({ error: '서버 오류가 발생했습니다.' });
}
});
/**
* 사용자 목록 조회 (관리자만)
*/
router.get('/users', verifyToken, requireAccess('admin'), (req, res) => {
try {
const userList = users.map(user => ({
user_id: user.user_id,
username: user.username,
name: user.name,
access_level: user.access_level,
worker_id: user.worker_id,
created_at: user.created_at
}));
res.json(userList);
} catch (error) {
console.error('Get users error:', error);
res.status(500).json({ error: '서버 오류가 발생했습니다.' });
}
});
/**
* 사용자 삭제 (관리자만)
*/
router.delete('/users/:id', verifyToken, requireAccess('admin'), (req, res) => {
try {
const userId = parseInt(req.params.id);
// 자기 자신 삭제 방지
if (userId === req.user.user_id) {
return res.status(400).json({ error: '자기 자신은 삭제할 수 없습니다.' });
}
const userIndex = users.findIndex(u => u.user_id === userId);
if (userIndex === -1) {
return res.status(404).json({ error: '사용자를 찾을 수 없습니다.' });
}
users.splice(userIndex, 1);
res.json({
success: true,
message: '사용자가 성공적으로 삭제되었습니다.'
});
} catch (error) {
console.error('Delete user error:', error);
res.status(500).json({ error: '서버 오류가 발생했습니다.' });
}
});
module.exports = router;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,21 @@
// routes/cuttingPlanRoutes.js
const express = require('express');
const router = express.Router();
const cuttingPlanController = require('../controllers/cuttingPlanController');
// CREATE
router.post('/', cuttingPlanController.createCuttingPlan);
// READ ALL
router.get('/', cuttingPlanController.getAllCuttingPlans);
// READ ONE
router.get('/:cutting_plan_id', cuttingPlanController.getCuttingPlanById);
// UPDATE
router.put('/:cutting_plan_id', cuttingPlanController.updateCuttingPlan);
// DELETE
router.delete('/:cutting_plan_id', cuttingPlanController.removeCuttingPlan);
module.exports = router;

View File

@@ -0,0 +1,20 @@
// routes/dailyIssueReportRoutes.js
const express = require('express');
const router = express.Router();
const {
createDailyIssueReport,
getDailyIssuesByDate,
removeDailyIssue
} = require('../controllers/dailyIssueReportController');
// 1. 등록 (단일 또는 다중)
router.post('/', createDailyIssueReport);
// 2. 날짜별 조회 (?date=YYYY-MM-DD 형식)
router.get('/', getDailyIssuesByDate);
// 3. 삭제
router.delete('/:id', removeDailyIssue);
module.exports = router;

View File

@@ -0,0 +1,72 @@
// routes/dailyWorkReportRoutes.js - 누적입력 방식 + 모든 기존 기능 포함
const express = require('express');
const router = express.Router();
const dailyWorkReportController = require('../controllers/dailyWorkReportController');
// 📋 마스터 데이터 조회 라우트들 (모든 인증된 사용자)
router.get('/work-types', dailyWorkReportController.getWorkTypes);
router.get('/work-status-types', dailyWorkReportController.getWorkStatusTypes);
router.get('/error-types', dailyWorkReportController.getErrorTypes);
// 🔄 누적 관련 새로운 라우트들 (누적입력 시스템 전용)
router.get('/accumulated', dailyWorkReportController.getAccumulatedReports); // ?date=2024-06-16&worker_id=1
router.get('/contributors', dailyWorkReportController.getContributorsSummary); // ?date=2024-06-16&worker_id=1
router.get('/my-data', dailyWorkReportController.getMyAccumulatedData); // ?date=2024-06-16&worker_id=1
// ✅ check-overwrite 엔드포인트 추가 (누락된 엔드포인트)
router.get('/check-overwrite', (req, res) => {
const { date, worker_id } = req.query;
if (!date || !worker_id) {
return res.status(400).json({
error: 'date와 worker_id가 필요합니다.',
example: 'date=2025-06-16&worker_id=1'
});
}
console.log(`🔍 덮어쓰기 권한 확인: 날짜=${date}, 작업자=${worker_id} (누적입력모드)`);
// 누적입력 시스템에서는 항상 덮어쓰기 가능 (실제로는 누적만 함)
res.json({
canOverwrite: true,
reason: 'accumulate_mode',
message: '누적입력 모드에서는 항상 추가 가능합니다.',
date,
worker_id,
timestamp: new Date().toISOString()
});
});
router.delete('/my-entry/:id', dailyWorkReportController.removeMyEntry); // 개별 항목 삭제 (본인 것만)
// 📅 월간 요약 (반드시 다른 라우트보다 먼저 정의)
router.get('/summary/monthly', dailyWorkReportController.getMonthlySummary);
// 📊 일일 근무 요약 조회
router.get('/summary', dailyWorkReportController.getDailySummary);
// 🔍 검색 (페이지네이션 포함)
router.get('/search', dailyWorkReportController.searchWorkReports);
// 📈 통계
router.get('/stats', dailyWorkReportController.getWorkReportStats);
// 📝 일일 작업보고서 생성 (누적 방식 - 덮어쓰기 없음!)
router.post('/', dailyWorkReportController.createDailyWorkReport);
// 📊 일일 작업보고서 조회 (날짜별 - 경로 파라미터)
router.get('/date/:date', dailyWorkReportController.getDailyWorkReportsByDate);
// 📊 일일 작업보고서 조회 (쿼리 파라미터 기반 - 작성자별 필터링)
router.get('/', dailyWorkReportController.getDailyWorkReports);
// ✏️ 작업보고서 수정
router.put('/:id', dailyWorkReportController.updateWorkReport);
// 🗑️ 작업자의 특정 날짜 전체 삭제
router.delete('/date/:date/worker/:worker_id', dailyWorkReportController.removeDailyWorkReportByDateAndWorker);
// 🗑️ 특정 작업보고서 삭제 (항상 가장 마지막에 정의)
router.delete('/:id', dailyWorkReportController.removeDailyWorkReport);
module.exports = router;

View File

@@ -0,0 +1,21 @@
// routes/equipmentListRoutes.js
const express = require('express');
const router = express.Router();
const equipmentListController = require('../controllers/equipmentListController');
// CREATE
router.post('/', equipmentListController.createEquipment);
// READ ALL
router.get('/', equipmentListController.getAllEquipment);
// READ ONE
router.get('/:equipment_id', equipmentListController.getEquipmentById);
// UPDATE
router.put('/:equipment_id', equipmentListController.updateEquipment);
// DELETE
router.delete('/:equipment_id', equipmentListController.removeEquipment);
module.exports = router;

View File

@@ -0,0 +1,42 @@
const express = require('express');
const router = express.Router();
const multer = require('multer');
const path = require('path');
const factoryInfoController = require('../controllers/factoryInfoController');
const { verifyToken } = require('../middlewares/authMiddleware'); // ← 수정
const { requireAccess } = require('../middlewares/accessMiddleware'); // ← 수정
// 📦 파일 저장 설정
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'public/uploads/');
},
filename: (req, file, cb) => {
const uniqueName = `map_image-${Date.now()}${path.extname(file.originalname)}`;
cb(null, uniqueName);
}
});
const upload = multer({ storage });
// CREATE
router.post(
'/',
verifyToken, // ← 수정
upload.single('map_image'),
factoryInfoController.createFactoryInfo
);
// READ ALL
router.get('/', verifyToken, factoryInfoController.getAllFactoryInfo); // ← 수정
// READ ONE
router.get('/:factory_id', verifyToken, factoryInfoController.getFactoryInfoById); // ← 수정
// UPDATE
router.put('/:factory_id', verifyToken, factoryInfoController.updateFactoryInfo); // ← 수정
// DELETE
router.delete('/:factory_id', verifyToken, factoryInfoController.removeFactoryInfo); // ← 수정
module.exports = router;

View File

@@ -0,0 +1,28 @@
// routes/healthRoutes.js
const express = require('express');
const router = express.Router();
// 헬스체크 엔드포인트
router.get('/', (req, res) => {
res.json({
status: 'healthy',
service: 'Technical Korea API',
timestamp: new Date().toISOString(),
uptime: process.uptime()
});
});
// 상세 헬스체크 (옵션)
router.get('/detail', (req, res) => {
res.json({
status: 'healthy',
service: 'Technical Korea API',
version: '2.1.0',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
memory: process.memoryUsage(),
environment: process.env.NODE_ENV || 'development'
});
});
module.exports = router;

View File

@@ -0,0 +1,10 @@
const express = require('express');
const router = express.Router();
const issueTypeController = require('../controllers/issueTypeController');
router.post('/', issueTypeController.createIssueType);
router.get('/', issueTypeController.getAllIssueTypes);
router.put('/:id', issueTypeController.updateIssueType);
router.delete('/:id', issueTypeController.removeIssueType);
module.exports = router;

View File

@@ -0,0 +1,8 @@
// routes/pingRoutes.js
const express = require('express');
const router = express.Router();
const pingController = require('../controllers/pingController');
router.get('/', pingController.ping);
module.exports = router;

View File

@@ -0,0 +1,55 @@
const express = require('express');
const router = express.Router();
const pipeSpecController = require('../controllers/pipeSpecController');
const auth = require('../middlewares/auth');
const { requireAccess } = require('../middlewares/access');
// ✅ 전체 조회 (모든 사용자 가능)
router.get(
'/',
auth,
requireAccess('worker', 'group_leader', 'support_team', 'admin', 'system'),
pipeSpecController.getAll
);
// ✅ 재질 목록
router.get(
'/materials',
auth,
requireAccess('worker', 'group_leader', 'support_team', 'admin', 'system'),
pipeSpecController.getMaterials
);
// ✅ 직경 목록
router.get(
'/diameters',
auth,
requireAccess('worker', 'group_leader', 'support_team', 'admin', 'system'),
pipeSpecController.getDiameters
);
// ✅ 스케줄 목록
router.get(
'/schedules',
auth,
requireAccess('worker', 'group_leader', 'support_team', 'admin', 'system'),
pipeSpecController.getSchedules
);
// ✅ 등록 (시스템 또는 관리자만)
router.post(
'/',
auth,
requireAccess('system', 'admin'),
pipeSpecController.create
);
// ✅ 삭제 (시스템 또는 관리자만)
router.delete(
'/:spec_id',
auth,
requireAccess('system', 'admin'),
pipeSpecController.remove
);
module.exports = router;

View File

@@ -0,0 +1,21 @@
// routes/processRoutes.js
const express = require('express');
const router = express.Router();
const processController = require('../controllers/processController');
// CREATE
router.post('/', processController.createProcess);
// READ ALL
router.get('/', processController.getAllProcesses);
// READ ONE
router.get('/:process_id', processController.getProcessById);
// UPDATE
router.put('/:process_id', processController.updateProcess);
// DELETE
router.delete('/:process_id', processController.removeProcess);
module.exports = router;

View File

@@ -0,0 +1,21 @@
// routes/projectRoutes.js
const express = require('express');
const router = express.Router();
const projectController = require('../controllers/projectController');
// CREATE
router.post('/', projectController.createProject);
// READ ALL
router.get('/', projectController.getAllProjects);
// READ ONE
router.get('/:project_id', projectController.getProjectById);
// UPDATE
router.put('/:project_id', projectController.updateProject);
// DELETE
router.delete('/:project_id', projectController.removeProject);
module.exports = router;

View File

@@ -0,0 +1,21 @@
// routes/taskRoutes.js
const express = require('express');
const router = express.Router();
const taskController = require('../controllers/taskController');
// CREATE
router.post('/', taskController.createTask);
// READ ALL
router.get('/', taskController.getAllTasks);
// READ ONE
router.get('/:task_id', taskController.getTaskById);
// UPDATE
router.put('/:task_id', taskController.updateTask);
// DELETE
router.delete('/:task_id', taskController.removeTask);
module.exports = router;

View File

@@ -0,0 +1,12 @@
// routes/toolsRoute.js
const express = require('express');
const router = express.Router();
const controller = require('../controllers/toolsController');
router.get('/', controller.getAll);
router.get('/:id', controller.getById);
router.post('/', controller.create);
router.put('/:id', controller.update);
router.delete('/:id', controller.delete);
module.exports = router;

View File

@@ -0,0 +1,25 @@
// ✅ routes/uploadBgRoutes.js (신규: 배경 이미지 전용 업로드 라우터)
const express = require('express');
const router = express.Router();
const multer = require('multer');
const path = require('path');
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, path.join(__dirname, '../public/img'));
},
filename: (req, file, cb) => {
cb(null, 'login-bg.jpeg'); // 고정된 파일명으로 덮어쓰기
}
});
const upload = multer({ storage });
router.post('/upload-bg', upload.single('image'), (req, res) => {
if (!req.file) {
return res.status(400).json({ success: false, message: '파일이 없습니다.' });
}
res.json({ success: true, path: '/img/login-bg.jpeg' });
});
module.exports = router;

View File

@@ -0,0 +1,10 @@
// ✅ routes/uploadRoutes.js (기존 업로드 라우터)
const express = require('express');
const router = express.Router();
const uploadController = require('../controllers/uploadController');
// 기존 업로드 등록/조회
router.post('/', uploadController.createUpload);
router.get('/', uploadController.getUploads);
module.exports = router;

View File

@@ -0,0 +1,74 @@
// routes/workAnalysis.js
const express = require('express');
const router = express.Router();
const workAnalysisController = require('../controllers/workAnalysisController');
// 🔒 분석 기능은 admin 또는 system 권한만 접근 가능
const requireAnalysisAccess = (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: '인증이 필요합니다.' });
}
const allowedLevels = ['admin', 'system'];
if (!allowedLevels.includes(req.user.access_level)) {
return res.status(403).json({
error: '분석 기능 접근 권한이 없습니다. 관리자 권한이 필요합니다.',
required: 'admin 또는 system',
current: req.user.access_level
});
}
console.log(`🔓 분석 기능 접근 허용: ${req.user.username} (${req.user.access_level})`);
next();
};
// 임시로 권한 체크 건너뛰기 (테스트용)
const skipAuth = (req, res, next) => {
console.log('⚠️ 임시로 권한 체크 건너뛰기');
next();
};
// 기본 통계 조회 - 임시로 권한 체크 비활성화
router.get('/stats', skipAuth, workAnalysisController.getStats);
// 일별 작업시간 추이 - 임시로 권한 체크 비활성화
router.get('/daily-trend', skipAuth, workAnalysisController.getDailyTrend);
// 작업자별 통계 - 임시로 권한 체크 비활성화
router.get('/worker-stats', skipAuth, workAnalysisController.getWorkerStats);
// 프로젝트별 통계 - 임시로 권한 체크 비활성화
router.get('/project-stats', skipAuth, workAnalysisController.getProjectStats);
// 작업유형별 통계 - 임시로 권한 체크 비활성화
router.get('/worktype-stats', skipAuth, workAnalysisController.getWorkTypeStats);
// 최근 작업 현황 - 임시로 권한 체크 비활성화
router.get('/recent-work', skipAuth, workAnalysisController.getRecentWork);
// 요일별 패턴 분석
router.get('/weekday-pattern', requireAnalysisAccess, workAnalysisController.getWeekdayPattern);
// 에러 분석
router.get('/error-analysis', requireAnalysisAccess, workAnalysisController.getErrorAnalysis);
// 월별 비교 분석
router.get('/monthly-comparison', requireAnalysisAccess, workAnalysisController.getMonthlyComparison);
// 작업자별 전문분야 분석
router.get('/worker-specialization', requireAnalysisAccess, workAnalysisController.getWorkerSpecialization);
// 대시보드용 종합 데이터 (한 번에 여러 데이터 조회)
router.get('/dashboard', requireAnalysisAccess, workAnalysisController.getDashboardData);
// 헬스체크 - 인증 없이 접근 가능
router.get('/health', (req, res) => {
res.status(200).json({
success: true,
message: 'Work Analysis API is running',
timestamp: new Date().toISOString()
});
});
module.exports = router;

View File

@@ -0,0 +1,61 @@
// routes/workAnalysisRoutes.js
const express = require('express');
const router = express.Router();
const workAnalysisController = require('../controllers/workAnalysisController');
// 🏠 대시보드용 종합 데이터 (가장 많이 사용될 것 같아서 맨 위에)
router.get('/dashboard', workAnalysisController.getDashboardData);
// 📊 기본 통계
router.get('/stats', workAnalysisController.getStats);
// 📈 일별 작업시간 추이
router.get('/daily-trend', workAnalysisController.getDailyTrend);
// 👥 작업자별 통계
router.get('/worker-stats', workAnalysisController.getWorkerStats);
// 📋 프로젝트별 통계
router.get('/project-stats', workAnalysisController.getProjectStats);
// 🔧 작업유형별 통계
router.get('/work-type-stats', workAnalysisController.getWorkTypeStats);
// 🕐 최근 작업 현황
router.get('/recent-work', workAnalysisController.getRecentWork);
// 📅 요일별 패턴 분석
router.get('/weekday-pattern', workAnalysisController.getWeekdayPattern);
// ❌ 에러 분석
router.get('/error-analysis', workAnalysisController.getErrorAnalysis);
// 📊 월별 비교 분석
router.get('/monthly-comparison', workAnalysisController.getMonthlyComparison);
// 🎯 작업자별 전문분야 분석
router.get('/worker-specialization', workAnalysisController.getWorkerSpecialization);
// 📋 헬스체크 및 API 정보
router.get('/health', (req, res) => {
res.json({
success: true,
message: '작업 분석 API가 정상 작동 중입니다.',
endpoints: [
'GET /work-analysis/dashboard - 대시보드 종합 데이터',
'GET /work-analysis/stats - 기본 통계',
'GET /work-analysis/daily-trend - 일별 추이',
'GET /work-analysis/worker-stats - 작업자별 통계',
'GET /work-analysis/project-stats - 프로젝트별 통계',
'GET /work-analysis/work-type-stats - 작업유형별 통계',
'GET /work-analysis/recent-work - 최근 작업 현황',
'GET /work-analysis/weekday-pattern - 요일별 패턴',
'GET /work-analysis/error-analysis - 에러 분석',
'GET /work-analysis/monthly-comparison - 월별 비교',
'GET /work-analysis/worker-specialization - 작업자 전문분야'
],
timestamp: new Date().toISOString()
});
});
module.exports = router;

View File

@@ -0,0 +1,26 @@
const express = require('express');
const router = express.Router();
const workReportController = require('../controllers/workReportController');
// CREATE
router.post('/', workReportController.createWorkReport);
// READ BY DATE
router.get('/date/:date', workReportController.getWorkReportsByDate);
// ✅ summary 라우트는 반드시 아래보다 위에 둬야 작동합니다
router.get('/summary', workReportController.getSummary);
// READ IN RANGE
router.get('/', workReportController.getWorkReportsInRange);
// READ ONE (항상 가장 마지막)
router.get('/:id', workReportController.getWorkReportById);
// UPDATE
router.put('/:id', workReportController.updateWorkReport);
// DELETE
router.delete('/:id', workReportController.removeWorkReport);
module.exports = router;

View File

@@ -0,0 +1,20 @@
const express = require('express');
const router = express.Router();
const workerController = require('../controllers/workerController');
// 작업자 생성
router.post('/', workerController.createWorker);
// 전체 작업자 조회
router.get('/', workerController.getAllWorkers);
// 특정 작업자 조회
router.get('/:worker_id', workerController.getWorkerById);
// 작업자 업데이트
router.put('/:worker_id', workerController.updateWorker);
// 작업자 삭제
router.delete('/:worker_id', workerController.removeWorker);
module.exports = router;