-
Notifications
You must be signed in to change notification settings - Fork 1
Feat/sw 70 - AI 북마크 분석 로직 고도화 및 API 에러 처리 표준화 #44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
2f22fe6
feat(link-analysis): 폴더 추천 알고리즘 개선 - 도메인 기반 카테고리 추론
jin2304 c95f76b
refactor(link-analysis): 폴더 추천 프롬프트 간결화 및 Step 2 우선순위 강화
jin2304 aa81773
feat(backend): AI 북마크 분석 로직 및 추천 성능 고도화
jin2304 833500f
fix(frontend): API 에러 처리 표준화 및 UI 상태 관리 개선
jin2304 f0eeca8
fix(frontend): 링크 저장 다이얼로그 태그 상태 및 인증 만료 처리 개선
jin2304 6f5a56a
feat(link-analysis): 폴더 추천 컨텍스트 확장 및 안정성 개선
jin2304 2f416d7
fix(auth): OAuth2 신규 가입 기본 폴더 생성 트랜잭션 보강
jin2304 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
131 changes: 131 additions & 0 deletions
131
src/main/java/com/web/SearchWeb/linkanalysis/domain/FolderContext.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| package com.web.SearchWeb.linkanalysis.domain; | ||
|
|
||
| import lombok.Builder; | ||
| import lombok.Getter; | ||
| import lombok.extern.slf4j.Slf4j; | ||
|
|
||
| import java.sql.Array; | ||
| import java.sql.SQLException; | ||
| import java.time.LocalDate; | ||
| import java.time.LocalDateTime; | ||
| import java.util.ArrayList; | ||
| import java.util.Collections; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
|
|
||
| /** | ||
| * 폴더 컨텍스트 도메인 객체 (LLM 폴더 추천용) | ||
| * - BookmarkDao.selectFolderContexts() 결과 행을 변환한 불변 DTO | ||
| * - 폴더당 북마크 수, 최근 저장일, 샘플 제목 목록, 자주 쓰인 태그(빈도) 목록을 포함 | ||
| */ | ||
| @Slf4j | ||
| @Getter | ||
| @Builder | ||
| public class FolderContext { | ||
|
|
||
| /** 폴더 ID */ | ||
| private final Long memberFolderId; | ||
|
|
||
| /** 폴더명 */ | ||
| private final String folderName; | ||
|
|
||
| /** 폴더 설명 (용도) */ | ||
| private final String description; | ||
|
|
||
| /** 폴더 유형 ("CUSTOM" / "UNORGANIZED") */ | ||
| private final String folderType; | ||
|
|
||
| /** 활성 북마크 개수 */ | ||
| private final long bookmarkCount; | ||
|
|
||
| /** 최근 북마크 저장 일자 (UNORGANIZED 또는 빈 폴더는 null) */ | ||
| private final LocalDate lastCreatedAt; | ||
|
|
||
| /** 샘플 북마크 제목 목록 (최신순 최대 sampleLimit개) */ | ||
| @Builder.Default | ||
| private final List<String> sampleTitles = List.of(); | ||
|
|
||
| /** 폴더 안의 저장 링크에서 자주 쓰인 태그(빈도) 목록. 예: AI(12) */ | ||
| @Builder.Default | ||
| private final List<String> topTags = List.of(); | ||
|
|
||
|
|
||
| /** | ||
| * MyBatis가 반환한 Map row 한 건을 FolderContext로 변환한다. | ||
| * - PostgreSQL ARRAY → java.sql.Array → List<String> 변환 처리 | ||
| * - timestamp → LocalDate 변환 처리 | ||
| */ | ||
| public static FolderContext fromRow(Map<String, Object> row) { | ||
| return FolderContext.builder() | ||
| .memberFolderId(toLong(row.get("memberFolderId"))) | ||
| .folderName(asString(row.get("folderName"))) | ||
| .description(asString(row.get("description"))) | ||
| .folderType(asString(row.get("folderType"))) | ||
| .bookmarkCount(toLong(row.get("bookmarkCount"))) | ||
| .lastCreatedAt(toLocalDate(row.get("lastCreatedAt"))) | ||
| .sampleTitles(toStringList(row.get("sampleTitles"))) | ||
| .topTags(toStringList(row.get("topTags"))) | ||
| .build(); | ||
| } | ||
|
|
||
|
|
||
| private static String asString(Object value) { | ||
| return value != null ? value.toString() : null; | ||
| } | ||
|
|
||
| private static long toLong(Object value) { | ||
| if (value == null) return 0L; | ||
| if (value instanceof Number n) return n.longValue(); | ||
| try { | ||
| return Long.parseLong(value.toString()); | ||
| } catch (NumberFormatException e) { | ||
| return 0L; | ||
| } | ||
| } | ||
|
|
||
| private static LocalDate toLocalDate(Object value) { | ||
| if (value == null) return null; | ||
| if (value instanceof LocalDate ld) return ld; | ||
| if (value instanceof LocalDateTime ldt) return ldt.toLocalDate(); | ||
| if (value instanceof java.sql.Date sqlDate) return sqlDate.toLocalDate(); | ||
| if (value instanceof java.sql.Timestamp ts) return ts.toLocalDateTime().toLocalDate(); | ||
| if (value instanceof java.util.Date d) { | ||
| return new java.sql.Timestamp(d.getTime()).toLocalDateTime().toLocalDate(); | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| @SuppressWarnings("unchecked") | ||
| private static List<String> toStringList(Object value) { | ||
| if (value == null) return List.of(); | ||
| if (value instanceof List<?> list) { | ||
| List<String> out = new ArrayList<>(list.size()); | ||
| for (Object o : list) { | ||
| if (o != null) out.add(o.toString()); | ||
| } | ||
| return Collections.unmodifiableList(out); | ||
| } | ||
| if (value instanceof Object[] arr) { | ||
| List<String> out = new ArrayList<>(arr.length); | ||
| for (Object o : arr) { | ||
| if (o != null) out.add(o.toString()); | ||
| } | ||
| return Collections.unmodifiableList(out); | ||
| } | ||
| if (value instanceof Array sqlArr) { | ||
| try { | ||
| Object inner = sqlArr.getArray(); | ||
| if (inner instanceof Object[] objArr) { | ||
| List<String> out = new ArrayList<>(objArr.length); | ||
| for (Object o : objArr) { | ||
| if (o != null) out.add(o.toString()); | ||
| } | ||
| return Collections.unmodifiableList(out); | ||
| } | ||
| } catch (SQLException e) { | ||
| log.debug("[folder-context] failed to read sql array: {}", e.getMessage()); | ||
| } | ||
| } | ||
|
Comment on lines
+115
to
+128
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial | 💤 Low value SQLException 무시 처리는 적절하나 로깅 추가 권장
🔧 선택적 로깅 추가+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
`@Getter`
`@Builder`
public class FolderContext {
// ...
} catch (SQLException ignored) {
- // fall through to empty
+ log.debug("Failed to convert SQL Array to List<String>", ignored);
}🤖 Prompt for AI Agents |
||
| return List.of(); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AUTH_EXPIRED에러 시alert()가/login리다이렉트를 차단fetchClient는 토큰 갱신 실패 시window.location.href = '/login'을 설정한 직후ApiError({ code: 'AUTH_EXPIRED' })를 throw합니다(fetchClient.ts Lines 108–115). React Query가 이를onError로 전달하면alert()가 실행되어 브라우저의 다이얼로그가 네비게이션을 블로킹합니다. 사용자는 알림을 직접 닫아야 로그인 페이지로 이동할 수 있습니다.이 동작은 해당
throw구문이 이번 PR에서 신규 추가(~)된 것이므로 새로 도입된 회귀입니다.🛡️ 제안 수정 — AUTH_EXPIRED alert 생략
📝 Committable suggestion
🤖 Prompt for AI Agents