- highlight-manager.js에서 showHighlightTooltip 함수 호출 시 배열 대신 단일 객체 전달하도록 수정 - 하이라이트 클릭 시 메모가 0개로 표시되던 문제 해결 - getOverlappingElements 함수에 디버깅 로그 추가 - 하이라이트 매니저 상태 확인 로그 추가 - 브라우저 캐시 무효화를 위한 버전 업데이트 (v=2025012617)
513 lines
16 KiB
JavaScript
513 lines
16 KiB
JavaScript
/**
|
|
* 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(`/highlight-notes/`, { note_document_id: documentId }).catch(() => []);
|
|
} else {
|
|
// 일반 문서의 하이라이트 메모
|
|
result = await this.api.get(`/highlight-notes/`, { document_id: documentId }).catch(() => []);
|
|
}
|
|
|
|
// 캐시에 저장
|
|
this.cache.set(cacheKey, result, 'notes', 10 * 60 * 1000);
|
|
console.log(`💾 메모 캐시 저장: ${documentId} (${result.length}개)`);
|
|
|
|
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('/highlight-notes/', data, {
|
|
invalidateCategories: ['notes', 'highlights']
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 메모 업데이트 (캐시 무효화)
|
|
*/
|
|
async updateNote(noteId, data) {
|
|
return await this.put(`/highlight-notes/${noteId}`, 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;
|