- 백엔드 API 완전 구현 (FastAPI + SQLAlchemy + PostgreSQL) - 사용자 인증 (JWT 토큰 기반) - 문서 CRUD (업로드, 조회, 목록, 삭제) - 하이라이트, 메모, 책갈피 관리 - 태그 시스템 및 검색 기능 - Pydantic v2 호환성 수정 - 프론트엔드 완전 구현 (Alpine.js + Tailwind CSS) - 로그인/로그아웃 기능 - 문서 업로드 모달 (드래그앤드롭, 파일 검증) - 문서 목록 및 필터링 - 뷰어 페이지 (하이라이트, 메모, 책갈피 UI) - 실시간 목록 새로고침 - 시스템 안정성 개선 - Alpine.js 컴포넌트 간 안전한 통신 (이벤트 기반) - API 오류 처리 및 사용자 피드백 - 파비콘 추가로 404 오류 해결 - 포트 구성: Frontend(24100), Backend(24102), DB(24101), Redis(24103)
92 lines
2.9 KiB
JavaScript
92 lines
2.9 KiB
JavaScript
/**
|
|
* 인증 관련 Alpine.js 컴포넌트
|
|
*/
|
|
|
|
// 인증 모달 컴포넌트
|
|
window.authModal = () => ({
|
|
showLogin: false,
|
|
loginForm: {
|
|
email: '',
|
|
password: ''
|
|
},
|
|
loginError: '',
|
|
loginLoading: false,
|
|
|
|
async login() {
|
|
this.loginLoading = true;
|
|
this.loginError = '';
|
|
|
|
try {
|
|
// 실제 API 호출
|
|
const response = await api.login(this.loginForm.email, this.loginForm.password);
|
|
|
|
// 토큰 저장
|
|
api.setToken(response.access_token);
|
|
localStorage.setItem('refresh_token', response.refresh_token);
|
|
|
|
// 사용자 정보 가져오기
|
|
const userResponse = await api.getCurrentUser();
|
|
|
|
// 전역 상태 업데이트
|
|
window.dispatchEvent(new CustomEvent('auth-changed', {
|
|
detail: { isAuthenticated: true, user: userResponse }
|
|
}));
|
|
|
|
// 모달 닫기 (부모 컴포넌트의 상태 변경)
|
|
window.dispatchEvent(new CustomEvent('close-login-modal'));
|
|
this.loginForm = { email: '', password: '' };
|
|
|
|
} catch (error) {
|
|
this.loginError = error.message || '로그인에 실패했습니다';
|
|
} finally {
|
|
this.loginLoading = false;
|
|
}
|
|
},
|
|
|
|
async logout() {
|
|
try {
|
|
await api.logout();
|
|
} catch (error) {
|
|
console.error('Logout error:', error);
|
|
} finally {
|
|
// 로컬 스토리지 정리
|
|
localStorage.removeItem('refresh_token');
|
|
|
|
// 전역 상태 업데이트
|
|
window.dispatchEvent(new CustomEvent('auth-changed', {
|
|
detail: { isAuthenticated: false, user: null }
|
|
}));
|
|
}
|
|
}
|
|
});
|
|
|
|
// 자동 토큰 갱신
|
|
async function refreshTokenIfNeeded() {
|
|
const refreshToken = localStorage.getItem('refresh_token');
|
|
if (!refreshToken || !api.token) return;
|
|
|
|
try {
|
|
// 토큰 만료 확인 (JWT 디코딩)
|
|
const tokenPayload = JSON.parse(atob(api.token.split('.')[1]));
|
|
const now = Date.now() / 1000;
|
|
|
|
// 토큰이 5분 내에 만료되면 갱신
|
|
if (tokenPayload.exp - now < 300) {
|
|
const response = await api.refreshToken(refreshToken);
|
|
api.setToken(response.access_token);
|
|
localStorage.setItem('refresh_token', response.refresh_token);
|
|
}
|
|
} catch (error) {
|
|
console.error('Token refresh failed:', error);
|
|
// 갱신 실패시 로그아웃
|
|
api.setToken(null);
|
|
localStorage.removeItem('refresh_token');
|
|
window.dispatchEvent(new CustomEvent('auth-changed', {
|
|
detail: { isAuthenticated: false, user: null }
|
|
}));
|
|
}
|
|
}
|
|
|
|
// 5분마다 토큰 갱신 체크
|
|
setInterval(refreshTokenIfNeeded, 5 * 60 * 1000);
|