/** * API 통신 유틸리티 */ class DocumentServerAPI { constructor() { // 도커 백엔드 API (24102 포트) this.baseURL = 'http://localhost:24102/api'; this.token = localStorage.getItem('access_token'); console.log('🐳 API Base URL (DOCKER BACKEND):', this.baseURL); console.log('🔧 도커 환경 설정 완료 - 버전 2025012384'); } // 토큰 설정 setToken(token) { this.token = token; if (token) { localStorage.setItem('access_token', token); } else { localStorage.removeItem('access_token'); } } // 기본 요청 헤더 getHeaders() { const headers = { 'Content-Type': 'application/json', }; if (this.token) { headers['Authorization'] = `Bearer ${this.token}`; } return headers; } // GET 요청 async get(endpoint, params = {}) { const url = new URL(`${this.baseURL}${endpoint}`, window.location.origin); Object.keys(params).forEach(key => { if (params[key] !== null && params[key] !== undefined) { url.searchParams.append(key, params[key]); } }); const response = await fetch(url, { method: 'GET', headers: this.getHeaders(), }); return this.handleResponse(response); } // POST 요청 async post(endpoint, data = {}) { const response = await fetch(`${this.baseURL}${endpoint}`, { method: 'POST', headers: this.getHeaders(), body: JSON.stringify(data), }); return this.handleResponse(response); } // PUT 요청 async put(endpoint, data = {}) { const response = await fetch(`${this.baseURL}${endpoint}`, { method: 'PUT', headers: this.getHeaders(), body: JSON.stringify(data), }); return this.handleResponse(response); } // DELETE 요청 async delete(endpoint) { const response = await fetch(`${this.baseURL}${endpoint}`, { method: 'DELETE', headers: this.getHeaders(), }); return this.handleResponse(response); } // 파일 업로드 async uploadFile(endpoint, formData) { const headers = {}; if (this.token) { headers['Authorization'] = `Bearer ${this.token}`; } const response = await fetch(`${this.baseURL}${endpoint}`, { method: 'POST', headers: headers, body: formData, }); return this.handleResponse(response); } // 응답 처리 async handleResponse(response) { if (response.status === 401) { // 토큰 만료 또는 인증 실패 this.setToken(null); window.location.reload(); throw new Error('인증이 필요합니다'); } if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error(errorData.detail || `HTTP ${response.status}`); } const contentType = response.headers.get('content-type'); if (contentType && contentType.includes('application/json')) { return await response.json(); } return await response.text(); } // 인증 관련 API async login(email, password) { const response = await this.post('/auth/login', { email, password }); // 토큰 저장 if (response.access_token) { this.setToken(response.access_token); // 사용자 정보 가져오기 try { const user = await this.getCurrentUser(); return { success: true, user: user, token: response.access_token }; } catch (error) { return { success: false, message: '사용자 정보를 가져올 수 없습니다.' }; } } else { return { success: false, message: '로그인에 실패했습니다.' }; } } async logout() { try { await this.post('/auth/logout'); } finally { this.setToken(null); } } async getCurrentUser() { return await this.get('/auth/me'); } async refreshToken(refreshToken) { return await this.post('/auth/refresh', { refresh_token: refreshToken }); } // 문서 관련 API async getDocuments(params = {}) { return await this.get('/documents/', params); } async getDocumentsHierarchy() { return await this.get('/documents/hierarchy/structured'); } async getDocument(documentId) { return await this.get(`/documents/${documentId}`); } async getDocumentContent(documentId) { return await this.get(`/documents/${documentId}/content`); } async uploadDocument(formData) { return await this.uploadFile('/documents/', formData); } async updateDocument(documentId, updateData) { return await this.put(`/documents/${documentId}`, updateData); } async deleteDocument(documentId) { return await this.delete(`/documents/${documentId}`); } async getTags() { return await this.get('/documents/tags/'); } async createTag(tagData) { return await this.post('/documents/tags/', tagData); } // 하이라이트 관련 API async createHighlight(highlightData) { return await this.post('/highlights/', highlightData); } async getDocumentHighlights(documentId) { return await this.get(`/highlights/document/${documentId}`); } async updateHighlight(highlightId, data) { return await this.put(`/highlights/${highlightId}`, data); } async deleteHighlight(highlightId) { return await this.delete(`/highlights/${highlightId}`); } // 메모 관련 API async createNote(noteData) { return await this.post('/notes/', noteData); } async getNotes(params = {}) { return await this.get('/notes/', params); } async getDocumentNotes(documentId) { return await this.get(`/notes/document/${documentId}`); } async updateNote(noteId, data) { return await this.put(`/notes/${noteId}`, data); } async deleteNote(noteId) { return await this.delete(`/notes/${noteId}`); } async getPopularNoteTags() { return await this.get('/notes/tags/popular'); } // 책갈피 관련 API async createBookmark(bookmarkData) { return await this.post('/bookmarks/', bookmarkData); } async getBookmarks(params = {}) { return await this.get('/bookmarks/', params); } async getDocumentBookmarks(documentId) { return await this.get(`/bookmarks/document/${documentId}`); } async updateBookmark(bookmarkId, data) { return await this.put(`/bookmarks/${bookmarkId}`, data); } async deleteBookmark(bookmarkId) { return await this.delete(`/bookmarks/${bookmarkId}`); } // 검색 관련 API async search(params = {}) { return await this.get('/search/', params); } async getSearchSuggestions(query) { return await this.get('/search/suggestions', { q: query }); } // 사용자 관리 API async getUsers() { return await this.get('/users/'); } async createUser(userData) { return await this.post('/auth/create-user', userData); } async updateUser(userId, userData) { return await this.put(`/users/${userId}`, userData); } async deleteUser(userId) { return await this.delete(`/users/${userId}`); } async updateProfile(profileData) { return await this.put('/users/profile', profileData); } async changePassword(passwordData) { return await this.put('/auth/change-password', passwordData); } // === 하이라이트 관련 API === async getDocumentHighlights(documentId) { return await this.get(`/highlights/document/${documentId}`); } async createHighlight(highlightData) { return await this.post('/highlights/', highlightData); } async updateHighlight(highlightId, highlightData) { return await this.put(`/highlights/${highlightId}`, highlightData); } async deleteHighlight(highlightId) { return await this.delete(`/highlights/${highlightId}`); } // === 메모 관련 API === async getDocumentNotes(documentId) { return await this.get(`/notes/document/${documentId}`); } async createNote(noteData) { return await this.post('/notes/', noteData); } async updateNote(noteId, noteData) { return await this.put(`/notes/${noteId}`, noteData); } async deleteNote(noteId) { return await this.delete(`/notes/${noteId}`); } async getNotesByHighlight(highlightId) { return await this.get(`/notes/highlight/${highlightId}`); } // === 책갈피 관련 API === async getDocumentBookmarks(documentId) { return await this.get(`/bookmarks/document/${documentId}`); } async createBookmark(bookmarkData) { return await this.post('/bookmarks/', bookmarkData); } async updateBookmark(bookmarkId, bookmarkData) { return await this.put(`/bookmarks/${bookmarkId}`, bookmarkData); } async deleteBookmark(bookmarkId) { return await this.delete(`/bookmarks/${bookmarkId}`); } // === 검색 관련 API === async searchDocuments(query, filters = {}) { const params = new URLSearchParams({ q: query, ...filters }); return await this.get(`/search/documents?${params}`); } async searchNotes(query, documentId = null) { const params = new URLSearchParams({ q: query }); if (documentId) params.append('document_id', documentId); return await this.get(`/search/notes?${params}`); } // === 서적 관련 API === async getBooks(skip = 0, limit = 50, search = null) { const params = new URLSearchParams({ skip, limit }); if (search) params.append('search', search); return await this.get(`/books?${params}`); } async createBook(bookData) { return await this.post('/books', bookData); } async getBook(bookId) { return await this.get(`/books/${bookId}`); } async updateBook(bookId, bookData) { return await this.put(`/books/${bookId}`, bookData); } async searchBooks(query, limit = 10) { const params = new URLSearchParams({ q: query, limit }); return await this.get(`/books/search/?${params}`); } async getBookSuggestions(title, limit = 5) { const params = new URLSearchParams({ title, limit }); return await this.get(`/books/suggestions/?${params}`); } // === 서적 소분류 관련 API === async createBookCategory(categoryData) { return await this.post('/book-categories/', categoryData); } async getBookCategories(bookId) { return await this.get(`/book-categories/book/${bookId}`); } async updateBookCategory(categoryId, categoryData) { return await this.put(`/book-categories/${categoryId}`, categoryData); } async deleteBookCategory(categoryId) { return await this.delete(`/book-categories/${categoryId}`); } async updateDocumentOrder(orderData) { return await this.put('/book-categories/documents/reorder', orderData); } // === 하이라이트 관련 API === async getDocumentHighlights(documentId) { return await this.get(`/highlights/document/${documentId}`); } async createHighlight(highlightData) { return await this.post('/highlights/', highlightData); } async updateHighlight(highlightId, highlightData) { return await this.put(`/highlights/${highlightId}`, highlightData); } async deleteHighlight(highlightId) { return await this.delete(`/highlights/${highlightId}`); } // === 메모 관련 API === async getDocumentNotes(documentId) { return await this.get(`/notes/document/${documentId}`); } async createNote(noteData) { return await this.post('/notes/', noteData); } async updateNote(noteId, noteData) { return await this.put(`/notes/${noteId}`, noteData); } async deleteNote(noteId) { return await this.delete(`/notes/${noteId}`); } // ============================================================================ // 트리 메모장 API // ============================================================================ // 메모 트리 관리 async getUserMemoTrees(includeArchived = false) { const params = includeArchived ? '?include_archived=true' : ''; return await this.get(`/memo-trees/${params}`); } async createMemoTree(treeData) { return await this.post('/memo-trees/', treeData); } async getMemoTree(treeId) { return await this.get(`/memo-trees/${treeId}`); } async updateMemoTree(treeId, treeData) { return await this.put(`/memo-trees/${treeId}`, treeData); } async deleteMemoTree(treeId) { return await this.delete(`/memo-trees/${treeId}`); } // 메모 노드 관리 async getMemoTreeNodes(treeId) { return await this.get(`/memo-trees/${treeId}/nodes`); } async createMemoNode(nodeData) { return await this.post(`/memo-trees/${nodeData.tree_id}/nodes`, nodeData); } async getMemoNode(nodeId) { return await this.get(`/memo-trees/nodes/${nodeId}`); } async updateMemoNode(nodeId, nodeData) { return await this.put(`/memo-trees/nodes/${nodeId}`, nodeData); } async deleteMemoNode(nodeId) { return await this.delete(`/memo-trees/nodes/${nodeId}`); } // 노드 이동 async moveMemoNode(nodeId, moveData) { return await this.put(`/memo-trees/nodes/${nodeId}/move`, moveData); } // 트리 통계 async getMemoTreeStats(treeId) { return await this.get(`/memo-trees/${treeId}/stats`); } // 검색 async searchMemoNodes(searchData) { return await this.post('/memo-trees/search', searchData); } // 내보내기 async exportMemoTree(exportData) { return await this.post('/memo-trees/export', exportData); } } // 전역 API 인스턴스 window.api = new DocumentServerAPI();