🚀 초기 프로젝트 설정 완료
✨ 기능: - 기간제 근로자 작업관리 시스템 기본 구조 - 한국어 기반 프론트엔드 (로그인, 대시보드, 작업자 관리) - Node.js Express 백엔드 API 서버 구조 - MySQL 데이터베이스 스키마 설계 - 14000번대 포트 구성으로 충돌 방지 📁 구조: - frontend/ : HTML, CSS, JS (Bootstrap 5) - backend/ : Node.js, Express, MySQL - database/ : 초기화 스크립트 - docs/ : 문서 🔌 포트: - 웹: 14000, API: 14001, DB: 14002, phpMyAdmin: 14003 🎯 다음 단계: 백엔드 API 라우트 구현 및 Docker 설정
This commit is contained in:
101
backend/models/database.js
Normal file
101
backend/models/database.js
Normal file
@@ -0,0 +1,101 @@
|
||||
const mysql = require('mysql2/promise');
|
||||
|
||||
// 데이터베이스 연결 풀 생성
|
||||
const pool = mysql.createPool({
|
||||
host: process.env.DB_HOST || 'localhost',
|
||||
port: process.env.DB_PORT || 3306,
|
||||
user: process.env.DB_USER || 'root',
|
||||
password: process.env.DB_PASSWORD || 'rootpassword',
|
||||
database: process.env.DB_NAME || 'worker_management',
|
||||
waitForConnections: true,
|
||||
connectionLimit: 10,
|
||||
queueLimit: 0,
|
||||
acquireTimeout: 60000,
|
||||
timeout: 60000,
|
||||
reconnect: true,
|
||||
charset: 'utf8mb4'
|
||||
});
|
||||
|
||||
// 데이터베이스 연결 테스트
|
||||
async function testConnection() {
|
||||
try {
|
||||
const connection = await pool.getConnection();
|
||||
console.log('✅ 데이터베이스 연결 성공');
|
||||
connection.release();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('❌ 데이터베이스 연결 실패:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 쿼리 실행 함수
|
||||
async function executeQuery(sql, params = []) {
|
||||
try {
|
||||
const [rows] = await pool.execute(sql, params);
|
||||
return rows;
|
||||
} catch (error) {
|
||||
console.error('쿼리 실행 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 트랜잭션 실행 함수
|
||||
async function executeTransaction(queries) {
|
||||
const connection = await pool.getConnection();
|
||||
|
||||
try {
|
||||
await connection.beginTransaction();
|
||||
|
||||
const results = [];
|
||||
for (const { sql, params } of queries) {
|
||||
const [result] = await connection.execute(sql, params);
|
||||
results.push(result);
|
||||
}
|
||||
|
||||
await connection.commit();
|
||||
return results;
|
||||
} catch (error) {
|
||||
await connection.rollback();
|
||||
throw error;
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
|
||||
// 페이지네이션 쿼리 함수
|
||||
async function executePagedQuery(sql, params = [], page = 1, limit = 10) {
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
// 전체 개수 조회
|
||||
const countSql = `SELECT COUNT(*) as total FROM (${sql}) as count_query`;
|
||||
const [countResult] = await pool.execute(countSql, params);
|
||||
const total = countResult[0].total;
|
||||
|
||||
// 페이지 데이터 조회
|
||||
const pagedSql = `${sql} LIMIT ? OFFSET ?`;
|
||||
const [rows] = await pool.execute(pagedSql, [...params, limit, offset]);
|
||||
|
||||
return {
|
||||
data: rows,
|
||||
pagination: {
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
hasNext: page < Math.ceil(total / limit),
|
||||
hasPrev: page > 1
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 데이터베이스 초기화 시 연결 테스트
|
||||
testConnection();
|
||||
|
||||
module.exports = {
|
||||
pool,
|
||||
executeQuery,
|
||||
executeTransaction,
|
||||
executePagedQuery,
|
||||
testConnection
|
||||
};
|
||||
37
backend/package.json
Normal file
37
backend/package.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "worker-management-backend",
|
||||
"version": "1.0.0",
|
||||
"description": "기간제 근로자 작업관리 시스템 백엔드 API",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"dev": "nodemon server.js",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [
|
||||
"worker",
|
||||
"management",
|
||||
"api",
|
||||
"express",
|
||||
"mysql"
|
||||
],
|
||||
"author": "Hyungi",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"mysql2": "^3.6.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.3.1",
|
||||
"helmet": "^7.0.0",
|
||||
"express-rate-limit": "^6.10.0",
|
||||
"joi": "^17.9.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
}
|
||||
102
backend/server.js
Normal file
102
backend/server.js
Normal file
@@ -0,0 +1,102 @@
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const helmet = require('helmet');
|
||||
const rateLimit = require('express-rate-limit');
|
||||
require('dotenv').config({ path: './config.env' });
|
||||
|
||||
const authRoutes = require('./routes/auth');
|
||||
const workerRoutes = require('./routes/workers');
|
||||
const dailyWorkRoutes = require('./routes/dailyWork');
|
||||
const errorRoutes = require('./routes/errors');
|
||||
const requestRoutes = require('./routes/requests');
|
||||
const dashboardRoutes = require('./routes/dashboard');
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 14001;
|
||||
|
||||
// 보안 미들웨어
|
||||
app.use(helmet());
|
||||
|
||||
// Rate limiting
|
||||
const limiter = rateLimit({
|
||||
windowMs: 15 * 60 * 1000, // 15분
|
||||
max: 100, // 최대 100개 요청
|
||||
message: {
|
||||
error: '너무 많은 요청이 발생했습니다. 잠시 후 다시 시도해주세요.'
|
||||
}
|
||||
});
|
||||
app.use('/api/', limiter);
|
||||
|
||||
// CORS 설정
|
||||
app.use(cors({
|
||||
origin: process.env.CORS_ORIGIN || 'http://localhost:3000',
|
||||
credentials: true
|
||||
}));
|
||||
|
||||
// Body parser
|
||||
app.use(express.json({ limit: '10mb' }));
|
||||
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
|
||||
|
||||
// 로깅 미들웨어
|
||||
app.use((req, res, next) => {
|
||||
console.log(`${new Date().toISOString()} - ${req.method} ${req.path}`);
|
||||
next();
|
||||
});
|
||||
|
||||
// API 라우트
|
||||
app.use('/api/auth', authRoutes);
|
||||
app.use('/api/workers', workerRoutes);
|
||||
app.use('/api/daily-work', dailyWorkRoutes);
|
||||
app.use('/api/errors', errorRoutes);
|
||||
app.use('/api/requests', requestRoutes);
|
||||
app.use('/api/dashboard', dashboardRoutes);
|
||||
|
||||
// 기본 라우트
|
||||
app.get('/', (req, res) => {
|
||||
res.json({
|
||||
message: '기간제 근로자 작업관리 시스템 API',
|
||||
version: '1.0.0',
|
||||
status: 'running',
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
});
|
||||
|
||||
// 404 에러 처리
|
||||
app.use((req, res) => {
|
||||
res.status(404).json({
|
||||
error: '요청한 리소스를 찾을 수 없습니다.',
|
||||
path: req.path,
|
||||
method: req.method
|
||||
});
|
||||
});
|
||||
|
||||
// 전역 에러 처리
|
||||
app.use((err, req, res, next) => {
|
||||
console.error('서버 오류:', err);
|
||||
|
||||
res.status(err.status || 500).json({
|
||||
error: process.env.NODE_ENV === 'production'
|
||||
? '서버 내부 오류가 발생했습니다.'
|
||||
: err.message,
|
||||
...(process.env.NODE_ENV !== 'production' && { stack: err.stack })
|
||||
});
|
||||
});
|
||||
|
||||
// 서버 시작
|
||||
app.listen(PORT, () => {
|
||||
console.log(`🚀 서버가 포트 ${PORT}에서 실행 중입니다.`);
|
||||
console.log(`📊 대시보드: http://localhost:${PORT}`);
|
||||
console.log(`🔧 환경: ${process.env.NODE_ENV}`);
|
||||
console.log(`🗄️ 데이터베이스: ${process.env.DB_HOST}:${process.env.DB_PORT}/${process.env.DB_NAME}`);
|
||||
});
|
||||
|
||||
// Graceful shutdown
|
||||
process.on('SIGTERM', () => {
|
||||
console.log('🛑 서버 종료 신호를 받았습니다.');
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
console.log('🛑 서버를 종료합니다.');
|
||||
process.exit(0);
|
||||
});
|
||||
Reference in New Issue
Block a user