/** * API 통신 유틸리티 */ class DocumentServerAPI { constructor() { // nginx 프록시를 통한 API 호출 (절대 경로로 강제) this.baseURL = `${window.location.origin}/api`; this.token = localStorage.getItem('access_token'); console.log('🌐 API Base URL (NGINX PROXY):', this.baseURL); console.log('🔧 현재 브라우저 위치:', window.location.origin); console.log('🔧 현재 브라우저 전체 URL:', window.location.href); console.log('🔧 nginx 프록시 환경 설정 완료 - 상대 경로 사용'); } // 토큰 설정 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 = {}) { // URL 생성 시 포트 유지를 위해 단순 문자열 연결 사용 let url = `${this.baseURL}${endpoint}`; // 쿼리 파라미터 추가 if (Object.keys(params).length > 0) { const searchParams = new URLSearchParams(); Object.keys(params).forEach(key => { if (params[key] !== null && params[key] !== undefined) { searchParams.append(key, params[key]); } }); url += `?${searchParams.toString()}`; } const response = await fetch(url, { method: 'GET', headers: this.getHeaders(), mode: 'cors', credentials: 'same-origin' }); return this.handleResponse(response); } // POST 요청 async post(endpoint, data = {}) { const url = `${this.baseURL}${endpoint}`; console.log('🌐 POST 요청 시작'); console.log(' - baseURL:', this.baseURL); console.log(' - endpoint:', endpoint); console.log(' - 최종 URL:', url); console.log(' - 데이터:', data); console.log('🔍 fetch 호출 직전 URL 검증:', url); console.log('🔍 URL 타입:', typeof url); console.log('🔍 URL 절대/상대 여부:', url.startsWith('http') ? '절대경로' : '상대경로'); const response = await fetch(url, { method: 'POST', headers: this.getHeaders(), body: JSON.stringify(data), mode: 'cors', credentials: 'same-origin' }); console.log('📡 POST 응답 받음:', response.url, response.status); console.log('📡 실제 요청된 URL:', response.url); return this.handleResponse(response); } // PUT 요청 async put(endpoint, data = {}) { const url = `${this.baseURL}${endpoint}`; console.log('🌐 PUT 요청 URL:', url); // 디버깅용 const response = await fetch(url, { method: 'PUT', headers: this.getHeaders(), body: JSON.stringify(data), mode: 'cors', credentials: 'same-origin' }); return this.handleResponse(response); } // DELETE 요청 async delete(endpoint) { const url = `${this.baseURL}${endpoint}`; console.log('🌐 DELETE 요청 URL:', url); // 디버깅용 const response = await fetch(url, { method: 'DELETE', headers: this.getHeaders(), mode: 'cors', credentials: 'same-origin' }); return this.handleResponse(response); } // 파일 업로드 async uploadFile(endpoint, formData) { const url = `${this.baseURL}${endpoint}`; console.log('🌐 UPLOAD 요청 URL:', url); // 디버깅용 const headers = {}; if (this.token) { headers['Authorization'] = `Bearer ${this.token}`; } const response = await fetch(url, { method: 'POST', headers: headers, body: formData, mode: 'cors', credentials: 'same-origin' }); 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) { console.log('🎨 createHighlight 메서드 호출됨:', highlightData); return await this.post('/highlights/', highlightData); } async getDocumentHighlights(documentId) { return await this.get(`/highlights/document/${documentId}`); } async updateHighlight(highlightId, updateData) { return await this.put(`/highlights/${highlightId}`, updateData); } async deleteHighlight(highlightId) { return await this.delete(`/highlights/${highlightId}`); } // === 하이라이트 메모 (Highlight Memo) 관련 API === // 용어 정의: 하이라이트에 달리는 짧은 코멘트 async createNote(noteData) { return await this.post('/highlight-notes/', noteData); } async getNotes(params = {}) { return await this.get('/highlight-notes/', params); } async updateNote(noteId, updateData) { return await this.put(`/highlight-notes/${noteId}`, updateData); } async deleteNote(noteId) { return await this.delete(`/highlight-notes/${noteId}`); } // === 문서 메모 조회 === // 용어 정의: 특정 문서의 모든 하이라이트 메모 조회 async getDocumentNotes(documentId) { return await this.get(`/highlight-notes/`, { document_id: documentId }); } 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) { console.log('🎨 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 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 getDocumentNavigation(documentId) { return await this.get(`/documents/${documentId}/navigation`); } 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) { console.log('🎨 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 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 async createDocumentLink(documentId, linkData) { return await this.post(`/documents/${documentId}/links`, linkData); } async getDocumentLinks(documentId) { return await this.get(`/documents/${documentId}/links`); } async getLinkableDocuments(documentId) { return await this.get(`/documents/${documentId}/linkable-documents`); } async updateDocumentLink(linkId, linkData) { return await this.put(`/documents/links/${linkId}`, linkData); } async deleteDocumentLink(linkId) { return await this.delete(`/documents/links/${linkId}`); } // 백링크 관련 API async getDocumentBacklinks(documentId) { return await this.get(`/documents/${documentId}/backlinks`); } async getDocumentLinkFragments(documentId) { return await this.get(`/documents/${documentId}/link-fragments`); } // ===== 노트 문서 관련 API ===== // 모든 노트 조회 async getNoteDocuments(params = {}) { return await this.get('/note-documents/', params); } // 특정 노트 조회 async getNoteDocument(noteId) { return await this.get(`/note-documents/${noteId}`); } // 특정 노트북의 노트들 조회 async getNotesInNotebook(notebookId) { return await this.get('/note-documents/', { notebook_id: notebookId }); } // === 노트 문서 (Note Document) 관련 API === // 용어 정의: 독립적인 문서 작성 (HTML 기반) async createNoteDocument(noteData) { return await this.post('/note-documents/', noteData); } // 노트 업데이트 async updateNoteDocument(noteId, noteData) { return await this.put(`/note-documents/${noteId}`, noteData); } // 노트 삭제 async deleteNoteDocument(noteId) { return await this.delete(`/note-documents/${noteId}`); } // 노트 HTML 내보내기 async exportNoteAsHTML(noteId) { const response = await fetch(`${this.baseURL}/note-documents/${noteId}/export/html`, { method: 'GET', headers: this.getHeaders(), }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.blob(); } // ===== 노트북 관련 API ===== // 모든 노트북 조회 async getNotebooks(params = {}) { return await this.get('/notebooks/', params); } // 특정 노트북 조회 async getNotebook(notebookId) { return await this.get(`/notebooks/${notebookId}`); } // === 노트북 (Notebook) 관련 API === // 용어 정의: 노트 문서들을 그룹화하는 폴더 async createNotebook(notebookData) { return await this.post('/notebooks/', notebookData); } // 노트북 업데이트 async updateNotebook(notebookId, notebookData) { return await this.put(`/notebooks/${notebookId}`, notebookData); } // 노트북 삭제 async deleteNotebook(notebookId, force = false) { return await this.delete(`/notebooks/${notebookId}?force=${force}`); } // 노트북 통계 async getNotebookStats() { return await this.get('/notebooks/stats'); } // 노트북의 노트들 조회 async getNotebookNotes(notebookId, params = {}) { return await this.get(`/notebooks/${notebookId}/notes`, params); } // 노트를 노트북에 추가 async addNoteToNotebook(notebookId, noteId) { return await this.post(`/notebooks/${notebookId}/notes/${noteId}`); } // 노트를 노트북에서 제거 async removeNoteFromNotebook(notebookId, noteId) { return await this.delete(`/notebooks/${notebookId}/notes/${noteId}`); } } // 전역 API 인스턴스 window.api = new DocumentServerAPI();