/** * API 통신 유틸리티 */ // 환경에 따른 API URL 설정 const API_BASE_URL = window.location.hostname === 'localhost' ? 'http://localhost:9000/api' // 로컬 개발 환경 : `${window.location.protocol}//${window.location.hostname}:9000/api`; // 시놀로지 배포 환경 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;