/** * CachedAPI - 캐싱이 적용된 API 래퍼 * 기존 DocumentServerAPI를 확장하여 캐싱 기능을 추가합니다. */ class CachedAPI { constructor(baseAPI) { this.api = baseAPI; this.cache = window.cacheManager; console.log('🚀 CachedAPI 초기화 완료'); } /** * 캐싱이 적용된 GET 요청 */ async get(endpoint, params = {}, options = {}) { const { useCache = true, category = 'api', ttl = null, forceRefresh = false } = options; // 캐시 키 생성 const cacheKey = this.generateCacheKey(endpoint, params); // 강제 새로고침이 아니고 캐시 사용 설정인 경우 캐시 확인 if (useCache && !forceRefresh) { const cached = this.cache.get(cacheKey, category); if (cached) { console.log(`🚀 API 캐시 사용: ${endpoint}`); return cached; } } try { console.log(`🌐 API 호출: ${endpoint}`); // 실제 백엔드 API 엔드포인트로 매핑 let response; if (endpoint === '/highlights' && params.document_id) { // 실제: /highlights/document/{documentId} response = await this.api.get(`/highlights/document/${params.document_id}`); } else if (endpoint === '/notes' && params.document_id) { // 실제: /notes/document/{documentId} response = await this.api.get(`/notes/document/${params.document_id}`); } else if (endpoint === '/bookmarks' && params.document_id) { // 실제: /bookmarks/document/{documentId} response = await this.api.get(`/bookmarks/document/${params.document_id}`); } else if (endpoint === '/document-links' && params.document_id) { // 실제: /documents/{documentId}/links response = await this.api.get(`/documents/${params.document_id}/links`); } else if (endpoint === '/document-links/backlinks' && params.target_document_id) { // 실제: /documents/{documentId}/backlinks response = await this.api.get(`/documents/${params.target_document_id}/backlinks`); } else if (endpoint.startsWith('/documents/') && endpoint.endsWith('/links')) { // /documents/{documentId}/links 패턴 const documentId = endpoint.split('/')[2]; console.log('🔗 CachedAPI: 링크 API 직접 호출:', documentId); response = await this.api.getDocumentLinks(documentId); } else if (endpoint.startsWith('/documents/') && endpoint.endsWith('/backlinks')) { // /documents/{documentId}/backlinks 패턴 const documentId = endpoint.split('/')[2]; console.log('🔗 CachedAPI: 백링크 API 직접 호출:', documentId); response = await this.api.getDocumentBacklinks(documentId); } else if (endpoint.startsWith('/documents/') && endpoint.match(/^\/documents\/[^\/]+$/)) { // /documents/{documentId} 패턴만 (추가 경로 없음) const documentId = endpoint.split('/')[2]; console.log('📄 CachedAPI: 문서 API 호출:', documentId); response = await this.api.getDocument(documentId); } else { // 기본 API 호출 (기존 방식) response = await this.api.get(endpoint, params); } // 성공적인 응답만 캐시에 저장 if (useCache && response) { this.cache.set(cacheKey, response, category, ttl); console.log(`💾 API 응답 캐시 저장: ${endpoint}`); } return response; } catch (error) { console.error(`❌ API 호출 실패: ${endpoint}`, error); throw error; } } /** * 캐싱이 적용된 POST 요청 (일반적으로 캐시하지 않음) */ async post(endpoint, data = {}, options = {}) { const { invalidateCache = true, invalidateCategories = [] } = options; try { const response = await this.api.post(endpoint, data); // POST 후 관련 캐시 무효화 if (invalidateCache) { this.invalidateRelatedCache(endpoint, invalidateCategories); } return response; } catch (error) { console.error(`❌ API POST 실패: ${endpoint}`, error); throw error; } } /** * 캐싱이 적용된 PUT 요청 */ async put(endpoint, data = {}, options = {}) { const { invalidateCache = true, invalidateCategories = [] } = options; try { const response = await this.api.put(endpoint, data); // PUT 후 관련 캐시 무효화 if (invalidateCache) { this.invalidateRelatedCache(endpoint, invalidateCategories); } return response; } catch (error) { console.error(`❌ API PUT 실패: ${endpoint}`, error); throw error; } } /** * 캐싱이 적용된 DELETE 요청 */ async delete(endpoint, options = {}) { const { invalidateCache = true, invalidateCategories = [] } = options; try { const response = await this.api.delete(endpoint); // DELETE 후 관련 캐시 무효화 if (invalidateCache) { this.invalidateRelatedCache(endpoint, invalidateCategories); } return response; } catch (error) { console.error(`❌ API DELETE 실패: ${endpoint}`, error); throw error; } } /** * 문서 데이터 조회 (캐싱 최적화) */ async getDocument(documentId, contentType = 'document') { const cacheKey = `document_${documentId}_${contentType}`; // 캐시 확인 const cached = this.cache.get(cacheKey, 'document'); if (cached) { console.log(`🚀 문서 캐시 사용: ${documentId}`); return cached; } // 기존 API 메서드 직접 사용 try { const result = await this.api.getDocument(documentId); // 캐시에 저장 this.cache.set(cacheKey, result, 'document', 30 * 60 * 1000); console.log(`💾 문서 캐시 저장: ${documentId}`); return result; } catch (error) { console.error('문서 로드 실패:', error); throw error; } } /** * 하이라이트 조회 (캐싱 최적화) */ async getHighlights(documentId, contentType = 'document') { const cacheKey = `highlights_${documentId}_${contentType}`; // 캐시 확인 const cached = this.cache.get(cacheKey, 'highlights'); if (cached) { console.log(`🚀 하이라이트 캐시 사용: ${documentId}`); return cached; } // 기존 API 메서드 직접 사용 try { let result; if (contentType === 'note') { result = await this.api.get(`/note/${documentId}/highlights`).catch(() => []); } else { result = await this.api.getDocumentHighlights(documentId).catch(() => []); } // 캐시에 저장 this.cache.set(cacheKey, result, 'highlights', 10 * 60 * 1000); console.log(`💾 하이라이트 캐시 저장: ${documentId}`); return result; } catch (error) { console.error('하이라이트 로드 실패:', error); return []; } } /** * 메모 조회 (캐싱 최적화) */ async getNotes(documentId, contentType = 'document') { const cacheKey = `notes_${documentId}_${contentType}`; // 캐시 확인 const cached = this.cache.get(cacheKey, 'notes'); if (cached) { console.log(`🚀 메모 캐시 사용: ${documentId}`); return cached; } // 기존 API 메서드 직접 사용 try { let result; if (contentType === 'note') { result = await this.api.get(`/note/${documentId}/notes`).catch(() => []); } else { result = await this.api.getDocumentNotes(documentId).catch(() => []); } // 캐시에 저장 this.cache.set(cacheKey, result, 'notes', 10 * 60 * 1000); console.log(`💾 메모 캐시 저장: ${documentId}`); return result; } catch (error) { console.error('메모 로드 실패:', error); return []; } } /** * 북마크 조회 (캐싱 최적화) */ async getBookmarks(documentId) { const cacheKey = `bookmarks_${documentId}`; // 캐시 확인 const cached = this.cache.get(cacheKey, 'bookmarks'); if (cached) { console.log(`🚀 북마크 캐시 사용: ${documentId}`); return cached; } // 기존 API 메서드 직접 사용 try { const result = await this.api.getDocumentBookmarks(documentId).catch(() => []); // 캐시에 저장 this.cache.set(cacheKey, result, 'bookmarks', 15 * 60 * 1000); console.log(`💾 북마크 캐시 저장: ${documentId}`); return result; } catch (error) { console.error('북마크 로드 실패:', error); return []; } } /** * 문서 링크 조회 (캐싱 최적화) */ async getDocumentLinks(documentId) { const cacheKey = `links_${documentId}`; // 캐시 확인 const cached = this.cache.get(cacheKey, 'links'); if (cached) { console.log(`🚀 문서 링크 캐시 사용: ${documentId}`); return cached; } // 기존 API 메서드 직접 사용 try { const result = await this.api.getDocumentLinks(documentId).catch(() => []); // 캐시에 저장 this.cache.set(cacheKey, result, 'links', 15 * 60 * 1000); console.log(`💾 문서 링크 캐시 저장: ${documentId}`); return result; } catch (error) { console.error('문서 링크 로드 실패:', error); return []; } } /** * 백링크 조회 (캐싱 최적화) */ async getBacklinks(documentId) { const cacheKey = `backlinks_${documentId}`; // 캐시 확인 const cached = this.cache.get(cacheKey, 'links'); if (cached) { console.log(`🚀 백링크 캐시 사용: ${documentId}`); return cached; } // 기존 API 메서드 직접 사용 try { const result = await this.api.getDocumentBacklinks(documentId).catch(() => []); // 캐시에 저장 this.cache.set(cacheKey, result, 'links', 15 * 60 * 1000); console.log(`💾 백링크 캐시 저장: ${documentId}`); return result; } catch (error) { console.error('백링크 로드 실패:', error); return []; } } /** * 네비게이션 정보 조회 (캐싱 최적화) */ async getNavigation(documentId, contentType = 'document') { return await this.get('/documents/navigation', { document_id: documentId, content_type: contentType }, { category: 'navigation', ttl: 60 * 60 * 1000 // 1시간 }); } /** * 하이라이트 생성 (캐시 무효화) */ async createHighlight(data) { return await this.post('/highlights/', data, { invalidateCategories: ['highlights', 'notes'] }); } /** * 메모 생성 (캐시 무효화) */ async createNote(data) { return await this.post('/notes/', data, { invalidateCategories: ['notes', 'highlights'] }); } /** * 북마크 생성 (캐시 무효화) */ async createBookmark(data) { return await this.post('/bookmarks/', data, { invalidateCategories: ['bookmarks'] }); } /** * 링크 생성 (캐시 무효화) */ async createDocumentLink(data) { return await this.post('/document-links/', data, { invalidateCategories: ['links'] }); } /** * 캐시 키 생성 */ generateCacheKey(endpoint, params) { const sortedParams = Object.keys(params) .sort() .reduce((result, key) => { result[key] = params[key]; return result; }, {}); return `${endpoint}_${JSON.stringify(sortedParams)}`; } /** * 관련 캐시 무효화 */ invalidateRelatedCache(endpoint, categories = []) { console.log(`🗑️ 캐시 무효화: ${endpoint}`); // 기본 무효화 규칙 const defaultInvalidations = { '/highlights': ['highlights', 'notes'], '/notes': ['notes', 'highlights'], '/bookmarks': ['bookmarks'], '/document-links': ['links'] }; // 엔드포인트별 기본 무효화 적용 for (const [pattern, cats] of Object.entries(defaultInvalidations)) { if (endpoint.includes(pattern)) { cats.forEach(cat => this.cache.deleteCategory(cat)); } } // 추가 무효화 카테고리 적용 categories.forEach(category => { this.cache.deleteCategory(category); }); } /** * 특정 문서의 모든 캐시 무효화 */ invalidateDocumentCache(documentId) { console.log(`🗑️ 문서 캐시 무효화: ${documentId}`); const categories = ['document', 'highlights', 'notes', 'bookmarks', 'links', 'navigation']; categories.forEach(category => { // 해당 문서 ID가 포함된 캐시만 삭제하는 것이 이상적이지만, // 간단하게 전체 카테고리를 무효화 this.cache.deleteCategory(category); }); } /** * 캐시 강제 새로고침 */ async refreshCache(endpoint, params = {}, category = 'api') { return await this.get(endpoint, params, { category, forceRefresh: true }); } /** * 캐시 통계 조회 */ getCacheStats() { return this.cache.getStats(); } /** * 캐시 리포트 조회 */ getCacheReport() { return this.cache.getReport(); } /** * 모든 캐시 삭제 */ clearAllCache() { this.cache.clear(); console.log('🗑️ 모든 API 캐시 삭제 완료'); } // 기존 API 메서드들을 그대로 위임 (캐싱이 필요 없는 경우) setToken(token) { return this.api.setToken(token); } getHeaders() { return this.api.getHeaders(); } /** * 문서 네비게이션 정보 조회 (캐싱 최적화) */ async getDocumentNavigation(documentId) { const cacheKey = `navigation_${documentId}`; return await this.get(`/documents/${documentId}/navigation`, {}, { category: 'navigation', cacheKey, ttl: 30 * 60 * 1000 // 30분 (네비게이션은 자주 변경되지 않음) }); } } // 기존 api 인스턴스를 캐싱 API로 래핑 if (window.api) { window.cachedApi = new CachedAPI(window.api); console.log('🚀 CachedAPI 래퍼 생성 완료'); } // 전역으로 내보내기 window.CachedAPI = CachedAPI;