/** * API 통신 유틸리티 */ // 환경에 따른 API URL 설정 const API_BASE_URL = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1' ? 'http://localhost:9000/api' // 로컬 개발 환경 : window.location.hostname === 'todo.hyungi.net' ? 'https://api-todo.hyungi.net/api' // API 서브도메인 (HTTPS 통일) : window.location.hostname === '192.168.219.116' ? 'http://192.168.219.116:9000/api' // IP 직접 접근 : window.location.protocol === 'https:' ? `https://${window.location.hostname}:9000/api` // HTTPS 환경 : `http://${window.location.hostname}:9000/api`; // HTTP 환경 class ApiClient { constructor() { this.token = localStorage.getItem('authToken'); } async request(endpoint, options = {}) { const url = `${API_BASE_URL}${endpoint}`; const config = { headers: { 'Content-Type': 'application/json', ...options.headers }, ...options }; // 인증 토큰 추가 if (this.token) { config.headers['Authorization'] = `Bearer ${this.token}`; console.log('API 요청에 토큰 포함:', this.token.substring(0, 20) + '...'); } else { console.warn('API 요청에 토큰이 없습니다!'); } try { const response = await fetch(url, config); if (!response.ok) { if (response.status === 401) { // 토큰 만료 시 로그아웃 console.error('인증 실패 - 토큰 제거 후 로그인 페이지로 이동'); // 토큰만 제거하고 페이지 리로드는 하지 않음 localStorage.removeItem('authToken'); localStorage.removeItem('currentUser'); // 무한 루프 방지: 이미 index.html이 아닌 경우만 리다이렉트 if (!window.location.pathname.endsWith('index.html') && window.location.pathname !== '/') { window.location.href = 'index.html'; } throw new Error('인증이 만료되었습니다. 다시 로그인해주세요.'); } throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const contentType = response.headers.get('content-type'); if (contentType && contentType.includes('application/json')) { return await response.json(); } return await response.text(); } catch (error) { console.error('API 요청 실패:', error); throw error; } } // GET 요청 async get(endpoint) { // 토큰 재로드 (로그인 후 토큰이 업데이트된 경우) this.token = localStorage.getItem('authToken'); return this.request(endpoint, { method: 'GET' }); } // POST 요청 async post(endpoint, data) { // 토큰 재로드 this.token = localStorage.getItem('authToken'); return this.request(endpoint, { method: 'POST', body: JSON.stringify(data) }); } // PUT 요청 async put(endpoint, data) { // 토큰 재로드 this.token = localStorage.getItem('authToken'); return this.request(endpoint, { method: 'PUT', body: JSON.stringify(data) }); } // DELETE 요청 async delete(endpoint) { // 토큰 재로드 this.token = localStorage.getItem('authToken'); return this.request(endpoint, { method: 'DELETE' }); } // 파일 업로드 async uploadFile(endpoint, formData) { // 토큰 재로드 this.token = localStorage.getItem('authToken'); return this.request(endpoint, { method: 'POST', headers: { // Content-Type을 설정하지 않음 (FormData가 자동으로 설정) }, body: formData }); } // 토큰 설정 setToken(token) { this.token = token; localStorage.setItem('authToken', token); } // 로그아웃 logout() { this.token = null; localStorage.removeItem('authToken'); localStorage.removeItem('currentUser'); window.location.reload(); } } // 전역 API 클라이언트 인스턴스 const api = new ApiClient(); // 인증 관련 API const AuthAPI = { async login(username, password) { const response = await api.post('/auth/login', { username, password }); if (response.access_token) { api.setToken(response.access_token); // 사용자 정보가 있으면 저장, 없으면 기본값 사용 const user = response.user || { username: 'hyungi', full_name: 'Administrator' }; localStorage.setItem('currentUser', JSON.stringify(user)); } return response; }, async logout() { try { await api.post('/auth/logout'); } catch (error) { console.error('로그아웃 API 호출 실패:', error); } finally { api.logout(); } }, async getCurrentUser() { return api.get('/auth/me'); } }; // Todo 관련 API const TodoAPI = { async getTodos(status = null, category = null) { let url = '/todos/'; // 슬래시 추가 const params = new URLSearchParams(); if (status && status !== 'all') params.append('status', status); if (category && category !== 'all') params.append('category', category); if (params.toString()) { url += '?' + params.toString(); } return api.get(url); }, async createTodo(todoData) { return api.post('/todos/', todoData); // 슬래시 추가 }, async updateTodo(id, todoData) { return api.put(`/todos/${id}`, todoData); }, async deleteTodo(id) { return api.delete(`/todos/${id}`); }, async completeTodo(id) { return api.put(`/todos/${id}`, { status: 'completed' }); }, async getTodayTodos() { return api.get('/calendar/today'); }, async getTodoById(id) { return api.get(`/todos/${id}`); }, async uploadImage(imageFile) { const formData = new FormData(); formData.append('image', imageFile); return api.uploadFile('/todos/upload-image', formData); } }; // 전역으로 사용 가능하도록 export window.api = api; window.AuthAPI = AuthAPI; window.TodoAPI = TodoAPI;