v1.0.0#82
Conversation
feat: 토큰 재발급 및 로그아웃 기능 구현
feat: Slack 알림 전송 구현
chore: docker compose가 .env를 사용할 수 있도록 개선
feat: 로컬 회원가입 및 로그인 API 구현
feat: 버그수정, 신규유저, 업데이트 시각 쿼리 추가
모니터링 SDK 추가
fix: 모니터링을 위해 추가 엔드포인트 인증 예외처리
feat: 로그 모니터링을 위한 환경 구축
fix: 인증/인가 관련 에러 로그 제거
Summary of ChangesHello @swthewhite, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request represents a foundational release, Highlights
Ignored Files
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
이 PR은 JWT를 사용한 보안, QueryDSL을 사용한 데이터베이스 엔티티 및 리포지토리, 다양한 유틸리티 클래스, 컨트롤러, 서비스, 그리고 Checkstyle, JaCoCo, Spotless, SonarQube와 같은 CI/CD 도구 설정을 포함하여 전체 백엔드 프로젝트 구조를 설정하는 대규모의 새로운 기능들을 도입하고 있습니다. 전반적인 구조는 잘 설계되었으며 최신 Java 및 Spring Boot 관행을 따르고 있습니다.
하지만, 몇 가지 중요하고 심각도가 높은 문제들이 해결되어야 합니다. 가장 중요한 것은 README.md 파일에 SonarCloud 토큰이 하드코딩되어 있다는 점으로, 이는 주요 보안 위험입니다. 또한 오류 처리의 일관성 부족, 잘못된 테스트 케이스, 그리고 정리해야 할 일부 코드가 있습니다.
아래에 이러한 문제들에 대한 구체적인 의견을 남겼습니다. 이 문제들이 해결되면 프로젝트의 매우 견고한 기반이 될 것입니다.
| [](https://sonarcloud.io/summary/new_code?id=ureca-mini2-div4_backend) | ||
| [](https://sonarcloud.io/summary/new_code?id=ureca-mini2-div4_backend) |
| ApiResponse<Void> apiResponse = ApiResponse.error(errorCode); | ||
| String responseBody = objectMapper.writeValueAsString(apiResponse); | ||
|
|
||
| response.setStatus(errorCode.getStatus().value()); |
There was a problem hiding this comment.
인증 실패 시 응답 상태 코드를 errorCode.getStatus().value()로 설정하고 있습니다. 이는 다른 예외 핸들러(GlobalExceptionHandler, JwtExceptionFilter)가 항상 200 OK를 반환하는 것과 일관되지 않습니다. API 응답의 일관성을 위해 이 핸들러도 HttpServletResponse.SC_OK를 반환하도록 수정하는 것을 권장합니다. API의 성공/실패 여부는 응답 본문의 success 필드로 판단하는 것이 일관된 설계입니다.
| response.setStatus(errorCode.getStatus().value()); | |
| response.setStatus(HttpServletResponse.SC_OK); |
| String expected = """ | ||
| 🏆 오늘의 랭킹 | ||
|
|
||
| 🥇 1위 유재석 - 7 solved (+48) | ||
| 🥈 2위 정형돈 - 5 solved (+32) | ||
| 🥉 3위 노홍철 - 4 solved (+30) | ||
| """; | ||
|
|
||
| String result = util.formatDailyRank(ranks); | ||
|
|
||
| assertThat(result).isEqualTo(expected); |
There was a problem hiding this comment.
dailyRankFormatTest 테스트 케이스가 실패할 것으로 보입니다. expected 문자열의 제목은 "🏆 오늘의 랭킹"이지만, MessageFormatUtil의 실제 구현은 "📊 오늘 진행 중인 랭킹"을 생성합니다. 또한, text block을 사용할 때 들여쓰기 문제로 인해 isEqualTo 비교가 실패할 수 있습니다. 테스트가 실제 구현과 일치하도록 수정해야 합니다.
| String expected = """ | |
| 🏆 오늘의 랭킹 | |
| 🥇 1위 유재석 - 7 solved (+48) | |
| 🥈 2위 정형돈 - 5 solved (+32) | |
| 🥉 3위 노홍철 - 4 solved (+30) | |
| """; | |
| String result = util.formatDailyRank(ranks); | |
| assertThat(result).isEqualTo(expected); | |
| String expected = "📊 오늘 진행 중인 랭킹\n\n" + | |
| "🥇 1위 유재석 - 7 solved (+48)\n" + | |
| "🥈 2위 정형돈 - 5 solved (+32)\n" + | |
| "🥉 3위 노홍철 - 4 solved (+30)\n"; | |
| String result = util.formatDailyRank(ranks); | |
| assertThat(result).isEqualTo(expected); |
| @@ -0,0 +1 @@ | |||
|
|
|||
| private final UserDetailsService securityUserDetailsService; | ||
| private final JwtProvider accessTokenProvider; | ||
| private final AccessDeniedHandler accessDeniedHandler; | ||
| private final AspectUtil aspectUtil; |
| /** private RankingRowExtendedResponse(RankingRowResponse base, boolean newUser) { | ||
| Objects.requireNonNull(base, "base must not be null"); | ||
|
|
||
| this.userId = base.getUserId(); | ||
| this.rank = base.getRank(); | ||
| this.tier = base.getTier(); | ||
| this.name = base.getName(); | ||
| this.totalScore = base.getTotalScore(); | ||
| this.solvedCount = base.getSolvedCount(); | ||
| this.baekjoonId = base.getBaekjoonId(); | ||
| this.team = base.getTeam(); | ||
| this.diff = base.getDiff(); | ||
| this.newUser = newUser; | ||
| } | ||
|
|
||
| // ✅ 정적 팩토리 메서드 | ||
| public static RankingRowExtendedResponse from(RankingRowResponse base, boolean newUser) { | ||
| return new RankingRowExtendedResponse(base, newUser); | ||
| } |
No description provided.