수정 수정 수정
This commit is contained in:
parent
4c9b9a2504
commit
de2c56518f
|
|
@ -1,5 +1,6 @@
|
||||||
const { getConnection } = require('./connection');
|
const { getConnection } = require('./connection');
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
|
const oracledb = require('oracledb');
|
||||||
|
|
||||||
// 사용자 생성
|
// 사용자 생성
|
||||||
async function createUser(userData) {
|
async function createUser(userData) {
|
||||||
|
|
@ -177,19 +178,37 @@ async function getUserApiKeys(userId) {
|
||||||
FROM API_KEYS
|
FROM API_KEYS
|
||||||
WHERE USER_ID = :userId
|
WHERE USER_ID = :userId
|
||||||
ORDER BY CREATED_AT DESC`,
|
ORDER BY CREATED_AT DESC`,
|
||||||
[userId]
|
[userId],
|
||||||
|
{
|
||||||
|
fetchInfo: {
|
||||||
|
"PERMISSIONS": { type: oracledb.STRING } // CLOB을 문자열로 변환
|
||||||
|
}
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return result.rows.map(row => ({
|
return result.rows.map(row => {
|
||||||
id: row[0],
|
let permissions = [];
|
||||||
keyName: row[1],
|
try {
|
||||||
apiKey: row[2],
|
// CLOB이 문자열로 변환되어 있어야 함
|
||||||
permissions: typeof row[3] === 'string' ? JSON.parse(row[3] || '[]') : (row[3] || []),
|
if (row[3] && typeof row[3] === 'string') {
|
||||||
usageCount: row[4],
|
permissions = JSON.parse(row[3]);
|
||||||
lastUsed: row[5],
|
}
|
||||||
createdAt: row[6],
|
} catch (e) {
|
||||||
isActive: row[7]
|
console.error('권한 파싱 오류:', row[3], e);
|
||||||
}));
|
permissions = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: row[0],
|
||||||
|
keyName: row[1],
|
||||||
|
apiKey: row[2],
|
||||||
|
permissions: permissions,
|
||||||
|
usageCount: row[4] || 0,
|
||||||
|
lastUsed: row[5],
|
||||||
|
createdAt: row[6],
|
||||||
|
isActive: row[7]
|
||||||
|
};
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('API 키 목록 조회 실패:', err);
|
console.error('API 키 목록 조회 실패:', err);
|
||||||
throw err;
|
throw err;
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,8 @@ echo "=========================================="
|
||||||
echo "완료 시간: $(date)"
|
echo "완료 시간: $(date)"
|
||||||
echo "✅ Git 최신 소스 업데이트가 완료되었습니다!"
|
echo "✅ Git 최신 소스 업데이트가 완료되었습니다!"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# stash 목록이 있으면 알림
|
# stash 목록이 있으면 알림
|
||||||
STASH_COUNT=$(git stash list | wc -l)
|
STASH_COUNT=$(git stash list | wc -l)
|
||||||
if [ $STASH_COUNT -gt 0 ]; then
|
if [ $STASH_COUNT -gt 0 ]; then
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ function apiLogger(req, res, next) {
|
||||||
// API 키 정보가 있으면 추가
|
// API 키 정보가 있으면 추가
|
||||||
if (req.apiKey) {
|
if (req.apiKey) {
|
||||||
logData.apiKeyId = req.apiKey.id;
|
logData.apiKeyId = req.apiKey.id;
|
||||||
logData.apiKeyName = req.apiKey.name;
|
logData.apiKeyName = req.apiKey.keyName;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 비동기로 로그 저장 (응답 속도에 영향 주지 않도록)
|
// 비동기로 로그 저장 (응답 속도에 영향 주지 않도록)
|
||||||
|
|
|
||||||
|
|
@ -1455,3 +1455,45 @@ body.admin .stats-grid .admin-only {
|
||||||
margin-bottom: 1.5rem;
|
margin-bottom: 1.5rem;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 권한 배지 스타일 */
|
||||||
|
.permissions-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
border-radius: 12px;
|
||||||
|
background-color: #e3f2fd;
|
||||||
|
color: #1565c0;
|
||||||
|
border: 1px solid #bbdefb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-badge:nth-child(1) {
|
||||||
|
background-color: #e8f5e8;
|
||||||
|
color: #2e7d32;
|
||||||
|
border-color: #c8e6c9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-badge:nth-child(2) {
|
||||||
|
background-color: #fff3e0;
|
||||||
|
color: #ef6c00;
|
||||||
|
border-color: #ffcc02;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-badge:nth-child(3) {
|
||||||
|
background-color: #ffebee;
|
||||||
|
color: #c62828;
|
||||||
|
border-color: #ffcdd2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-none {
|
||||||
|
color: #6c757d;
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
@ -926,22 +926,22 @@ curl -X DELETE http://localhost:5577/api/users/user005 \
|
||||||
|
|
||||||
<!-- 빠른 테스트 예제 -->
|
<!-- 빠른 테스트 예제 -->
|
||||||
<div class="quick-tests">
|
<div class="quick-tests">
|
||||||
<h3>빠른 테스트 예제</h3>
|
<h3>빠른 테스트 예제 - User CRUD</h3>
|
||||||
<div class="test-examples">
|
<div class="test-examples">
|
||||||
<button onclick="loadExample('getData')" class="btn btn-info btn-sm">
|
<button onclick="loadExample('getAllUsers')" class="btn btn-info btn-sm">
|
||||||
<i class="fas fa-download"></i> 데이터 조회 (GET)
|
<i class="fas fa-users"></i> 모든 사용자 조회 (GET)
|
||||||
</button>
|
</button>
|
||||||
<button onclick="loadExample('createData')" class="btn btn-success btn-sm">
|
<button onclick="loadExample('getUser')" class="btn btn-primary btn-sm">
|
||||||
<i class="fas fa-plus"></i> 데이터 생성 (POST)
|
<i class="fas fa-user"></i> 특정 사용자 조회 (GET)
|
||||||
</button>
|
</button>
|
||||||
<button onclick="loadExample('updateData')" class="btn btn-warning btn-sm">
|
<button onclick="loadExample('createUser')" class="btn btn-success btn-sm">
|
||||||
<i class="fas fa-edit"></i> 데이터 수정 (PUT)
|
<i class="fas fa-user-plus"></i> 사용자 생성 (POST)
|
||||||
</button>
|
</button>
|
||||||
<button onclick="loadExample('deleteData')" class="btn btn-danger btn-sm">
|
<button onclick="loadExample('updateUser')" class="btn btn-warning btn-sm">
|
||||||
<i class="fas fa-trash"></i> 데이터 삭제 (DELETE)
|
<i class="fas fa-user-edit"></i> 사용자 수정 (PUT)
|
||||||
</button>
|
</button>
|
||||||
<button onclick="loadExample('getUserData')" class="btn btn-primary btn-sm">
|
<button onclick="loadExample('deleteUser')" class="btn btn-danger btn-sm">
|
||||||
<i class="fas fa-user"></i> 사용자 데이터 조회
|
<i class="fas fa-user-times"></i> 사용자 삭제 (DELETE)
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
111
public/js/app.js
111
public/js/app.js
|
|
@ -94,6 +94,16 @@ function showDashboard() {
|
||||||
|
|
||||||
// 초기 데이터 로드
|
// 초기 데이터 로드
|
||||||
loadDashboardData();
|
loadDashboardData();
|
||||||
|
|
||||||
|
// API 키 사용 횟수 실시간 업데이트를 위한 주기적 새로고침 (30초마다)
|
||||||
|
if (window.apiKeyRefreshInterval) {
|
||||||
|
clearInterval(window.apiKeyRefreshInterval);
|
||||||
|
}
|
||||||
|
window.apiKeyRefreshInterval = setInterval(() => {
|
||||||
|
if (document.getElementById('apiKeysSection').style.display !== 'none') {
|
||||||
|
loadApiKeys();
|
||||||
|
}
|
||||||
|
}, 30000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 토큰 검증
|
// 토큰 검증
|
||||||
|
|
@ -202,6 +212,13 @@ function handleLogout() {
|
||||||
authToken = null;
|
authToken = null;
|
||||||
currentUser = null;
|
currentUser = null;
|
||||||
document.body.classList.remove('admin');
|
document.body.classList.remove('admin');
|
||||||
|
|
||||||
|
// API 키 새로고침 인터벌 정리
|
||||||
|
if (window.apiKeyRefreshInterval) {
|
||||||
|
clearInterval(window.apiKeyRefreshInterval);
|
||||||
|
window.apiKeyRefreshInterval = null;
|
||||||
|
}
|
||||||
|
|
||||||
showLoginScreen();
|
showLoginScreen();
|
||||||
showToast('로그아웃되었습니다', 'info');
|
showToast('로그아웃되었습니다', 'info');
|
||||||
}
|
}
|
||||||
|
|
@ -311,7 +328,6 @@ async function loadApiKeys() {
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
console.log('API 키 데이터:', data); // 디버깅용
|
|
||||||
displayApiKeys(data.apiKeys || []);
|
displayApiKeys(data.apiKeys || []);
|
||||||
} else {
|
} else {
|
||||||
console.error('API 키 로드 실패:', response.status, response.statusText);
|
console.error('API 키 로드 실패:', response.status, response.statusText);
|
||||||
|
|
@ -325,6 +341,17 @@ async function loadApiKeys() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 권한 라벨 반환
|
||||||
|
function getPermissionLabel(permission) {
|
||||||
|
const labels = {
|
||||||
|
'read': '읽기',
|
||||||
|
'write': '쓰기',
|
||||||
|
'delete': '삭제',
|
||||||
|
'admin': '관리자'
|
||||||
|
};
|
||||||
|
return labels[permission] || permission;
|
||||||
|
}
|
||||||
|
|
||||||
// API 키 표시
|
// API 키 표시
|
||||||
function displayApiKeys(apiKeys) {
|
function displayApiKeys(apiKeys) {
|
||||||
const tbody = document.querySelector('#apiKeysTable tbody');
|
const tbody = document.querySelector('#apiKeysTable tbody');
|
||||||
|
|
@ -366,7 +393,14 @@ function displayApiKeys(apiKeys) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>${Array.isArray(key.permissions) ? key.permissions.join(', ') : (key.permissions || '없음')}</td>
|
<td>
|
||||||
|
<div class="permissions-list">
|
||||||
|
${Array.isArray(key.permissions) && key.permissions.length > 0
|
||||||
|
? key.permissions.map(p => `<span class="permission-badge">${getPermissionLabel(p)}</span>`).join(' ')
|
||||||
|
: '<span class="permission-none">권한 없음</span>'
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
<td>${key.usageCount || 0}</td>
|
<td>${key.usageCount || 0}</td>
|
||||||
<td>${key.lastUsed ? new Date(key.lastUsed).toLocaleString() : '사용 안함'}</td>
|
<td>${key.lastUsed ? new Date(key.lastUsed).toLocaleString() : '사용 안함'}</td>
|
||||||
<td>${new Date(key.createdAt).toLocaleString()}</td>
|
<td>${new Date(key.createdAt).toLocaleString()}</td>
|
||||||
|
|
@ -1308,44 +1342,47 @@ function loadExample(type) {
|
||||||
const bodyGroup = document.getElementById('requestBodyGroup');
|
const bodyGroup = document.getElementById('requestBodyGroup');
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'getData':
|
case 'getAllUsers':
|
||||||
methodSelect.value = 'GET';
|
|
||||||
urlInput.value = '/api/data';
|
|
||||||
bodyGroup.style.display = 'none';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'createData':
|
|
||||||
methodSelect.value = 'POST';
|
|
||||||
urlInput.value = '/api/data';
|
|
||||||
bodyTextarea.value = JSON.stringify({
|
|
||||||
name: "테스트 데이터",
|
|
||||||
description: "API 테스터에서 생성한 테스트 데이터입니다.",
|
|
||||||
dataValue: "테스트 값"
|
|
||||||
}, null, 2);
|
|
||||||
bodyGroup.style.display = 'block';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'updateData':
|
|
||||||
methodSelect.value = 'PUT';
|
|
||||||
urlInput.value = '/api/data/1';
|
|
||||||
bodyTextarea.value = JSON.stringify({
|
|
||||||
name: "수정된 데이터",
|
|
||||||
description: "API 테스터에서 수정한 데이터입니다.",
|
|
||||||
dataValue: "수정된 값"
|
|
||||||
}, null, 2);
|
|
||||||
bodyGroup.style.display = 'block';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'deleteData':
|
|
||||||
methodSelect.value = 'DELETE';
|
|
||||||
urlInput.value = '/api/data/1';
|
|
||||||
bodyGroup.style.display = 'none';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'getUserData':
|
|
||||||
methodSelect.value = 'GET';
|
methodSelect.value = 'GET';
|
||||||
urlInput.value = '/api/users';
|
urlInput.value = '/api/users';
|
||||||
bodyGroup.style.display = 'none';
|
bodyGroup.style.display = 'none';
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'getUser':
|
||||||
|
methodSelect.value = 'GET';
|
||||||
|
urlInput.value = '/api/users/user001';
|
||||||
|
bodyGroup.style.display = 'none';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'createUser':
|
||||||
|
methodSelect.value = 'POST';
|
||||||
|
urlInput.value = '/api/users';
|
||||||
|
bodyTextarea.value = JSON.stringify({
|
||||||
|
USER_ID: "user999",
|
||||||
|
USER_NAME: "홍길동",
|
||||||
|
DEPT_CODE: "IT001",
|
||||||
|
EMAIL: "hong@example.com",
|
||||||
|
PHONE: "010-1234-5678"
|
||||||
|
}, null, 2);
|
||||||
|
bodyGroup.style.display = 'block';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'updateUser':
|
||||||
|
methodSelect.value = 'PUT';
|
||||||
|
urlInput.value = '/api/users/user999';
|
||||||
|
bodyTextarea.value = JSON.stringify({
|
||||||
|
USER_NAME: "홍길동(수정)",
|
||||||
|
DEPT_CODE: "IT002",
|
||||||
|
EMAIL: "hong.updated@example.com",
|
||||||
|
PHONE: "010-9876-5432"
|
||||||
|
}, null, 2);
|
||||||
|
bodyGroup.style.display = 'block';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'deleteUser':
|
||||||
|
methodSelect.value = 'DELETE';
|
||||||
|
urlInput.value = '/api/users/user999';
|
||||||
|
bodyGroup.style.display = 'none';
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ const {
|
||||||
const { verifyApiKey, optionalAuth, requirePermission } = require('../middleware/auth');
|
const { verifyApiKey, optionalAuth, requirePermission } = require('../middleware/auth');
|
||||||
|
|
||||||
// 모든 데이터 조회 (API 키 또는 JWT 토큰 필요)
|
// 모든 데이터 조회 (API 키 또는 JWT 토큰 필요)
|
||||||
router.get('/data', optionalAuth, async (req, res) => {
|
router.get('/data', verifyApiKey, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const data = await getAllData();
|
const data = await getAllData();
|
||||||
res.json({
|
res.json({
|
||||||
|
|
@ -28,7 +28,7 @@ router.get('/data', optionalAuth, async (req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// 특정 ID 데이터 조회 (API 키 또는 JWT 토큰 필요)
|
// 특정 ID 데이터 조회 (API 키 또는 JWT 토큰 필요)
|
||||||
router.get('/data/:id', optionalAuth, async (req, res) => {
|
router.get('/data/:id', verifyApiKey, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const data = await getDataById(id);
|
const data = await getDataById(id);
|
||||||
|
|
@ -55,7 +55,7 @@ router.get('/data/:id', optionalAuth, async (req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// 데이터 생성 (API 키 또는 JWT 토큰 필요)
|
// 데이터 생성 (API 키 또는 JWT 토큰 필요)
|
||||||
router.post('/data', optionalAuth, async (req, res) => {
|
router.post('/data', verifyApiKey, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { name, description, dataValue } = req.body;
|
const { name, description, dataValue } = req.body;
|
||||||
|
|
||||||
|
|
@ -87,7 +87,7 @@ router.post('/data', optionalAuth, async (req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// 데이터 업데이트 (API 키 또는 JWT 토큰 필요)
|
// 데이터 업데이트 (API 키 또는 JWT 토큰 필요)
|
||||||
router.put('/data/:id', optionalAuth, async (req, res) => {
|
router.put('/data/:id', verifyApiKey, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const { name, description, dataValue } = req.body;
|
const { name, description, dataValue } = req.body;
|
||||||
|
|
@ -127,7 +127,7 @@ router.put('/data/:id', optionalAuth, async (req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// 데이터 삭제 (API 키 또는 JWT 토큰 필요)
|
// 데이터 삭제 (API 키 또는 JWT 토큰 필요)
|
||||||
router.delete('/data/:id', optionalAuth, async (req, res) => {
|
router.delete('/data/:id', verifyApiKey, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const result = await deleteData(id);
|
const result = await deleteData(id);
|
||||||
|
|
|
||||||
|
|
@ -139,7 +139,9 @@ router.post('/api-keys', verifyToken, async (req, res) => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await createApiKey(req.user.id, keyName, permissions || []);
|
// 기본 권한 설정 (권한이 지정되지 않은 경우)
|
||||||
|
const defaultPermissions = permissions && permissions.length > 0 ? permissions : ['read', 'write'];
|
||||||
|
const result = await createApiKey(req.user.id, keyName, defaultPermissions);
|
||||||
|
|
||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
success: true,
|
success: true,
|
||||||
|
|
@ -163,12 +165,11 @@ router.get('/api-keys', verifyToken, async (req, res) => {
|
||||||
const apiKeys = await getUserApiKeys(req.user.id);
|
const apiKeys = await getUserApiKeys(req.user.id);
|
||||||
|
|
||||||
// 전체 API 키를 반환 (사용자가 자신의 키를 관리할 수 있도록)
|
// 전체 API 키를 반환 (사용자가 자신의 키를 관리할 수 있도록)
|
||||||
// JSON 직렬화 안전한 데이터로 변환
|
|
||||||
const safeApiKeys = apiKeys.map(key => ({
|
const safeApiKeys = apiKeys.map(key => ({
|
||||||
id: key.id,
|
id: key.id,
|
||||||
keyName: key.keyName,
|
keyName: key.keyName,
|
||||||
apiKey: key.apiKey,
|
apiKey: key.apiKey,
|
||||||
permissions: Array.isArray(key.permissions) ? key.permissions : [],
|
permissions: key.permissions || [],
|
||||||
usageCount: key.usageCount || 0,
|
usageCount: key.usageCount || 0,
|
||||||
lastUsed: key.lastUsed ? new Date(key.lastUsed).toISOString() : null,
|
lastUsed: key.lastUsed ? new Date(key.lastUsed).toISOString() : null,
|
||||||
createdAt: key.createdAt ? new Date(key.createdAt).toISOString() : new Date().toISOString(),
|
createdAt: key.createdAt ? new Date(key.createdAt).toISOString() : new Date().toISOString(),
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const { getApiLogs, getApiLogStats } = require('../database/log-queries');
|
const { getApiLogs, getApiLogStats } = require('../database/log-queries');
|
||||||
const { verifyToken, requirePermission } = require('../middleware/auth');
|
const { verifyToken, requireAdmin } = require('../middleware/auth');
|
||||||
|
|
||||||
// API 로그 목록 조회 (관리자만)
|
// API 로그 목록 조회 (관리자만)
|
||||||
router.get('/api-logs', verifyToken, requirePermission('admin'), async (req, res) => {
|
router.get('/api-logs', verifyToken, requireAdmin, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const page = parseInt(req.query.page) || 1;
|
const page = parseInt(req.query.page) || 1;
|
||||||
const limit = parseInt(req.query.limit) || 50;
|
const limit = parseInt(req.query.limit) || 50;
|
||||||
|
|
@ -25,7 +25,7 @@ router.get('/api-logs', verifyToken, requirePermission('admin'), async (req, res
|
||||||
});
|
});
|
||||||
|
|
||||||
// API 로그 통계 조회 (관리자만)
|
// API 로그 통계 조회 (관리자만)
|
||||||
router.get('/api-logs/stats', verifyToken, requirePermission('admin'), async (req, res) => {
|
router.get('/api-logs/stats', verifyToken, requireAdmin, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const stats = await getApiLogStats();
|
const stats = await getApiLogStats();
|
||||||
res.json(stats);
|
res.json(stats);
|
||||||
|
|
|
||||||
22
server.js
22
server.js
|
|
@ -22,17 +22,31 @@ app.use(cors());
|
||||||
app.use(bodyParser.json({ limit: '50mb' }));
|
app.use(bodyParser.json({ limit: '50mb' }));
|
||||||
app.use(bodyParser.urlencoded({ extended: true, limit: '50mb' }));
|
app.use(bodyParser.urlencoded({ extended: true, limit: '50mb' }));
|
||||||
|
|
||||||
// 정적 파일 서빙
|
// 정적 파일 서빙 (개발 환경에서 캐시 비활성화)
|
||||||
app.use(express.static(path.join(__dirname, 'public')));
|
app.use(express.static(path.join(__dirname, 'public'), {
|
||||||
|
etag: false,
|
||||||
|
lastModified: false,
|
||||||
|
setHeaders: (res, path) => {
|
||||||
|
// 개발 환경에서 캐시 비활성화
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
||||||
|
res.setHeader('Pragma', 'no-cache');
|
||||||
|
res.setHeader('Expires', '0');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
// API 로깅 미들웨어 (정적 파일과 헬스체크는 제외)
|
// API 로깅 미들웨어 (정적 파일, 헬스체크, 로그 조회는 제외)
|
||||||
app.use(skipLogging([
|
app.use(skipLogging([
|
||||||
'/favicon.ico',
|
'/favicon.ico',
|
||||||
'/api/health',
|
'/api/health',
|
||||||
|
'/logs/api-logs',
|
||||||
|
'/logs/api-logs/stats',
|
||||||
/^\/css\//,
|
/^\/css\//,
|
||||||
/^\/js\//,
|
/^\/js\//,
|
||||||
/^\/images\//,
|
/^\/images\//,
|
||||||
/^\/fonts\//
|
/^\/fonts\//,
|
||||||
|
/^\/auth\// // 인증 관련 경로도 제외
|
||||||
]));
|
]));
|
||||||
|
|
||||||
// 요청 로깅 미들웨어
|
// 요청 로깅 미들웨어
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue