diff --git a/frontend/index.html b/frontend/index.html
index 01a3dcc..03480f4 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -478,6 +478,7 @@
+
@@ -544,66 +545,59 @@
}
}
- // API 로드 후 앱 초기화
+ // API 로드 후 앱 초기화 (AuthManager 사용)
async function initializeApp() {
- console.log('🚀 앱 초기화 시작');
+ console.log('🚀 앱 초기화 시작 (AuthManager 사용)');
- // 토큰이 있으면 사용자 정보 가져오기
- const token = localStorage.getItem('access_token');
- if (token) {
- try {
- // 토큰으로 사용자 정보 가져오기 (API 호출)
- const user = await AuthAPI.getCurrentUser();
+ try {
+ // AuthManager를 통한 인증 체크 (캐시 우선, 필요시에만 API 호출)
+ const user = await window.authManager.checkAuth();
+
+ if (user) {
currentUser = user;
- // localStorage에도 백업 저장
- localStorage.setItem('currentUser', JSON.stringify(user));
+ // 공통 헤더 초기화
+ console.log('🔧 공통 헤더 초기화 시작:', user.username);
- // 공통 헤더 초기화
- console.log('🔧 공통 헤더 초기화 시작:', user);
- console.log('window.commonHeader 존재:', !!window.commonHeader);
-
- if (window.commonHeader && typeof window.commonHeader.init === 'function') {
- await window.commonHeader.init(user, 'issues_create');
- console.log('✅ 공통 헤더 초기화 완료');
- } else {
- console.error('❌ 공통 헤더 모듈이 로드되지 않음');
- // 대안: 기본 사용자 정보 표시
- setTimeout(() => {
- if (window.commonHeader && typeof window.commonHeader.init === 'function') {
- console.log('🔄 지연된 공통 헤더 초기화');
- window.commonHeader.init(user, 'issues_create');
- }
- }, 200);
- }
+ if (window.commonHeader && typeof window.commonHeader.init === 'function') {
+ await window.commonHeader.init(user, 'issues_create');
+ console.log('✅ 공통 헤더 초기화 완료');
+ } else {
+ console.error('❌ 공통 헤더 모듈이 로드되지 않음');
+ setTimeout(() => {
+ if (window.commonHeader && typeof window.commonHeader.init === 'function') {
+ console.log('🔄 지연된 공통 헤더 초기화');
+ window.commonHeader.init(user, 'issues_create');
+ }
+ }, 200);
+ }
- // 페이지 접근 권한 체크 (부적합 등록 페이지)
+ // 페이지 접근 권한 체크
setTimeout(() => {
- if (!canAccessPage('issues_create')) {
+ if (typeof canAccessPage === 'function' && !canAccessPage('issues_create')) {
alert('부적합 등록 페이지에 접근할 권한이 없습니다.');
window.location.href = '/issue-view.html';
return;
}
}, 500);
- // 사용자 정보는 공통 헤더에서 표시됨
+ // 메인 화면 표시
document.getElementById('loginScreen').classList.add('hidden');
document.getElementById('mainScreen').classList.remove('hidden');
- // 프로젝트 로드
+ // 데이터 로드
await loadProjects();
-
loadIssues();
-
- // URL 해시 처리
handleUrlHash();
- } catch (error) {
- console.error('토큰 검증 실패:', error);
- // 토큰이 유효하지 않으면 로그아웃
- localStorage.removeItem('access_token');
- localStorage.removeItem('currentUser');
+ } else {
+ console.log('❌ 인증되지 않은 사용자 - 로그인 화면 표시');
+ // 로그인 화면은 이미 기본으로 표시됨
}
+
+ } catch (error) {
+ console.error('❌ 앱 초기화 실패:', error);
+ // 로그인 화면 표시 (기본 상태)
}
}
@@ -612,41 +606,43 @@
console.log('📄 DOM 로드 완료 - API 스크립트 로딩 대기 중...');
});
- // 로그인
+ // 로그인 (AuthManager 사용)
document.getElementById('loginForm').addEventListener('submit', async (e) => {
e.preventDefault();
const userId = document.getElementById('userId').value;
const password = document.getElementById('password').value;
try {
- const data = await AuthAPI.login(userId, password);
+ console.log('🔑 AuthManager를 통한 로그인 시도');
+ const data = await window.authManager.login(userId, password);
currentUser = data.user;
- // 토큰과 사용자 정보 저장
- localStorage.setItem('access_token', data.access_token);
- localStorage.setItem('currentUser', JSON.stringify(currentUser));
+ console.log('✅ 로그인 성공 - 메인 화면 초기화');
- // 사용자 정보는 공통 헤더에서 표시됨
+ // 공통 헤더 초기화
+ if (window.commonHeader && typeof window.commonHeader.init === 'function') {
+ await window.commonHeader.init(currentUser, 'issues_create');
+ }
+
+ // 메인 화면 표시
document.getElementById('loginScreen').classList.add('hidden');
document.getElementById('mainScreen').classList.remove('hidden');
- // 공통 헤더에서 권한 기반 메뉴 처리됨
-
- // 프로젝트 로드
+ // 데이터 로드
await loadProjects();
-
loadIssues();
-
- // URL 해시 처리
handleUrlHash();
+
} catch (error) {
+ console.error('❌ 로그인 실패:', error);
alert(error.message || '로그인에 실패했습니다.');
}
});
- // 로그아웃
+ // 로그아웃 (AuthManager 사용)
function logout() {
- AuthAPI.logout();
+ console.log('🚪 AuthManager를 통한 로그아웃');
+ window.authManager.logout();
}
// 네비게이션은 공통 헤더에서 처리됨
diff --git a/frontend/static/js/core/auth-manager.js b/frontend/static/js/core/auth-manager.js
new file mode 100644
index 0000000..2c3c079
--- /dev/null
+++ b/frontend/static/js/core/auth-manager.js
@@ -0,0 +1,263 @@
+/**
+ * 중앙화된 인증 관리자
+ * 페이지 간 이동 시 불필요한 API 호출을 방지하고 인증 상태를 효율적으로 관리
+ */
+class AuthManager {
+ constructor() {
+ this.currentUser = null;
+ this.isAuthenticated = false;
+ this.lastAuthCheck = null;
+ this.authCheckInterval = 5 * 60 * 1000; // 5분마다 토큰 유효성 체크
+ this.listeners = new Set();
+
+ // 초기화
+ this.init();
+ }
+
+ /**
+ * 초기화
+ */
+ init() {
+ console.log('🔐 AuthManager 초기화');
+
+ // localStorage에서 사용자 정보 복원
+ this.restoreUserFromStorage();
+
+ // 토큰 만료 체크 타이머 설정
+ this.setupTokenExpiryCheck();
+
+ // 페이지 가시성 변경 시 토큰 체크
+ document.addEventListener('visibilitychange', () => {
+ if (!document.hidden && this.shouldCheckAuth()) {
+ this.refreshAuth();
+ }
+ });
+ }
+
+ /**
+ * localStorage에서 사용자 정보 복원
+ */
+ restoreUserFromStorage() {
+ const token = localStorage.getItem('access_token');
+ const userStr = localStorage.getItem('currentUser');
+
+ if (token && userStr) {
+ try {
+ this.currentUser = JSON.parse(userStr);
+ this.isAuthenticated = true;
+ this.lastAuthCheck = Date.now();
+ console.log('✅ 저장된 사용자 정보 복원:', this.currentUser.username);
+ } catch (error) {
+ console.error('❌ 사용자 정보 복원 실패:', error);
+ this.clearAuth();
+ }
+ }
+ }
+
+ /**
+ * 인증이 필요한지 확인
+ */
+ shouldCheckAuth() {
+ if (!this.isAuthenticated) return true;
+ if (!this.lastAuthCheck) return true;
+
+ const timeSinceLastCheck = Date.now() - this.lastAuthCheck;
+ return timeSinceLastCheck > this.authCheckInterval;
+ }
+
+ /**
+ * 인증 상태 확인 (필요시에만 API 호출)
+ */
+ async checkAuth() {
+ console.log('🔍 인증 상태 확인 시작');
+
+ const token = localStorage.getItem('access_token');
+ if (!token) {
+ console.log('❌ 토큰 없음');
+ this.clearAuth();
+ return null;
+ }
+
+ // 최근에 체크했으면 캐시된 정보 사용
+ if (this.isAuthenticated && !this.shouldCheckAuth()) {
+ console.log('✅ 캐시된 인증 정보 사용:', this.currentUser.username);
+ return this.currentUser;
+ }
+
+ // API 호출이 필요한 경우
+ return await this.refreshAuth();
+ }
+
+ /**
+ * 강제로 인증 정보 새로고침 (API 호출)
+ */
+ async refreshAuth() {
+ console.log('🔄 인증 정보 새로고침 (API 호출)');
+
+ try {
+ // API가 로드될 때까지 대기
+ await this.waitForAPI();
+
+ const user = await AuthAPI.getCurrentUser();
+
+ this.currentUser = user;
+ this.isAuthenticated = true;
+ this.lastAuthCheck = Date.now();
+
+ // localStorage 업데이트
+ localStorage.setItem('currentUser', JSON.stringify(user));
+
+ console.log('✅ 인증 정보 새로고침 완료:', user.username);
+
+ // 리스너들에게 알림
+ this.notifyListeners('auth-success', user);
+
+ return user;
+
+ } catch (error) {
+ console.error('❌ 인증 실패:', error);
+ this.clearAuth();
+ this.notifyListeners('auth-failed', error);
+ throw error;
+ }
+ }
+
+ /**
+ * API 로드 대기
+ */
+ async waitForAPI() {
+ let attempts = 0;
+ const maxAttempts = 50;
+
+ while (typeof AuthAPI === 'undefined' && attempts < maxAttempts) {
+ await new Promise(resolve => setTimeout(resolve, 100));
+ attempts++;
+ }
+
+ if (typeof AuthAPI === 'undefined') {
+ throw new Error('AuthAPI를 로드할 수 없습니다');
+ }
+ }
+
+ /**
+ * 인증 정보 클리어
+ */
+ clearAuth() {
+ console.log('🧹 인증 정보 클리어');
+
+ this.currentUser = null;
+ this.isAuthenticated = false;
+ this.lastAuthCheck = null;
+
+ localStorage.removeItem('access_token');
+ localStorage.removeItem('currentUser');
+
+ this.notifyListeners('auth-cleared');
+ }
+
+ /**
+ * 로그인 처리
+ */
+ async login(username, password) {
+ console.log('🔑 로그인 시도:', username);
+
+ try {
+ await this.waitForAPI();
+ const data = await AuthAPI.login(username, password);
+
+ this.currentUser = data.user;
+ this.isAuthenticated = true;
+ this.lastAuthCheck = Date.now();
+
+ // localStorage 저장
+ localStorage.setItem('access_token', data.access_token);
+ localStorage.setItem('currentUser', JSON.stringify(data.user));
+
+ console.log('✅ 로그인 성공:', data.user.username);
+
+ this.notifyListeners('login-success', data.user);
+
+ return data;
+
+ } catch (error) {
+ console.error('❌ 로그인 실패:', error);
+ this.clearAuth();
+ throw error;
+ }
+ }
+
+ /**
+ * 로그아웃 처리
+ */
+ logout() {
+ console.log('🚪 로그아웃');
+
+ this.clearAuth();
+ this.notifyListeners('logout');
+
+ // 로그인 페이지로 이동
+ window.location.href = '/index.html';
+ }
+
+ /**
+ * 토큰 만료 체크 타이머 설정
+ */
+ setupTokenExpiryCheck() {
+ // 30분마다 토큰 유효성 체크
+ setInterval(() => {
+ if (this.isAuthenticated) {
+ console.log('⏰ 정기 토큰 유효성 체크');
+ this.refreshAuth().catch(() => {
+ console.log('🔄 토큰 만료 - 로그아웃 처리');
+ this.logout();
+ });
+ }
+ }, 30 * 60 * 1000);
+ }
+
+ /**
+ * 이벤트 리스너 등록
+ */
+ addEventListener(callback) {
+ this.listeners.add(callback);
+ }
+
+ /**
+ * 이벤트 리스너 제거
+ */
+ removeEventListener(callback) {
+ this.listeners.delete(callback);
+ }
+
+ /**
+ * 리스너들에게 알림
+ */
+ notifyListeners(event, data = null) {
+ this.listeners.forEach(callback => {
+ try {
+ callback(event, data);
+ } catch (error) {
+ console.error('리스너 콜백 오류:', error);
+ }
+ });
+ }
+
+ /**
+ * 현재 사용자 정보 반환
+ */
+ getCurrentUser() {
+ return this.currentUser;
+ }
+
+ /**
+ * 인증 상태 반환
+ */
+ isLoggedIn() {
+ return this.isAuthenticated && !!this.currentUser;
+ }
+}
+
+// 전역 인스턴스 생성
+window.authManager = new AuthManager();
+
+console.log('🎯 AuthManager 로드 완료');