Conversation
- api/v1/user/{userId} 엔드포인트 개발
- 비정규화 컬럼 추가
- .http 테스트 추가
워크스루유저 프로필 조회 기능을 구현하고 사용자 데이터 구조를 개선합니다. 전화번호 필드를 제거하고, 팔로워/팔로우/모임 참여/생성 횟수를 추적하기 위한 비정규화된 카운트 필드를 사용자 엔티티에 추가하며, H2 콘솔 접근을 공개 엔드포인트로 등록합니다. 변경사항
시퀀스 다이어그램sequenceDiagram
participant Client
participant UserController
participant UserService
participant UserRepository
participant DB as Database
participant UserInfoResponse
Client->>UserController: GET /api/v1/user/{userId}
UserController->>UserService: getProfile(userId)
UserService->>UserRepository: findById(userId)
UserRepository->>DB: SELECT * FROM user WHERE id=?
DB-->>UserRepository: User 엔티티
alt User 존재
UserRepository-->>UserService: User 반환
UserService->>UserInfoResponse: from(user)
UserInfoResponse-->>UserService: UserInfoResponse 생성
UserService-->>UserController: UserInfoResponse 반환
UserController-->>Client: 200 OK {userInfo}
else User 미존재
UserRepository-->>UserService: Optional.empty()
UserService->>UserService: throw UserNotFoundException
UserService-->>Client: 예외 발생
end
코드 검토 예상 소요 시간🎯 2 (단순) | ⏱️ ~12분
관련 이슈
관련 PR
제안 레이블
🐰 기념 시
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (6)
src/test/http/user/user-api.http (1)
20-26: 테스트에서 동적 userId 사용을 권장합니다.현재 하드코딩된 userId (1)는 새로운 데이터베이스 환경에서 존재하지 않을 수 있습니다. 회원가입/로그인 응답에서 userId를 추출하여 사용하는 것이 더 안정적입니다.
다음과 같이 개선할 수 있습니다:
> {% client.global.set("accessToken", response.body.data.accessToken); + client.global.set("userId", response.body.data.userId); %} ### 보호된 API 요청 (테스트용) -GET http://localhost:8080/api/v1/user/1 +GET http://localhost:8080/api/v1/user/{{userId}} Authorization: Bearer {{accessToken}}참고: 이를 위해서는 SignupResponse 또는 LoginResponse에 userId 필드가 포함되어야 합니다.
src/main/java/team/wego/wegobackend/user/presentation/UserController.java (1)
40-41: Path variable 유효성 검증을 추가하는 것을 고려하세요.음수 userId 같은 잘못된 입력값에 대한 검증이 없습니다.
@PathVariable @Min(1) Long userId를 사용하거나 서비스 레이어에서 검증하는 것을 권장합니다.- @GetMapping("{userId}") - public ResponseEntity<ApiResponse<UserInfoResponse>> profile(@PathVariable Long userId) { + @GetMapping("{userId}") + public ResponseEntity<ApiResponse<UserInfoResponse>> profile( + @PathVariable @Min(value = 1, message = "userId는 1 이상이어야 합니다") Long userId) {참고: 이를 위해서는
javax.validation.constraints.Min임포트가 필요합니다.src/main/java/team/wego/wegobackend/user/application/UserService.java (1)
11-11: 미사용 @slf4j 어노테이션을 제거하세요.클래스 내에서 로거(log)를 사용하지 않으므로 @slf4j 어노테이션을 제거하는 것이 좋습니다.
-@Slf4j @Service @RequiredArgsConstructor public class UserService {src/main/java/team/wego/wegobackend/user/application/dto/response/UserInfoResponse.java (1)
30-38: 필드 그룹화를 개선하여 가독성을 높이는 것을 고려하세요.카운트 관련 필드들(
followeesCnt,followersCnt,groupJoinedCnt,groupCreatedCnt)이isNotificationEnabled필드에 의해 분리되어 있습니다. 관련 필드를 함께 그룹화하면 가독성이 향상됩니다.private int followeesCnt; private int followersCnt; private int groupJoinedCnt; private int groupCreatedCnt; private Boolean isNotificationEnabled; + + private LocalDateTime createdAt;또는 카운트 필드들을 모두 함께 배치:
private String profileMessage; private int followeesCnt; - private int followersCnt; - private int groupJoinedCnt; - private int groupCreatedCnt; private Boolean isNotificationEnabled;src/main/java/team/wego/wegobackend/user/domain/User.java (2)
52-63: 카운트 컬럼의 null 허용 여부와 DB 기본값을 명확히 해두는 편이 안전합니다.
int필드에 대해@Column에nullable = false가 빠져 있는데, DDL에서 NOT NULL + 기본값 0이 보장되지 않으면 JPA가NULL을 primitiveint로 매핑하면서 문제가 생길 수 있습니다.
엔티티/DDL 둘 중 한쪽에서라도 반드시 “NULL 불가 + 기본 0”을 명시해 두는지 확인하시고, 가능하다면 아래처럼nullable = false를 추가해 두는 것을 권장합니다.- @Column(name = "followee_count") + @Column(name = "followee_count", nullable = false) private int followeesCnt; - @Column(name = "follower_count") + @Column(name = "follower_count", nullable = false) private int followersCnt; - @Column(name = "group_joined_count") + @Column(name = "group_joined_count", nullable = false) private int groupJoinedCnt; - @Column(name = "group_created_count") + @Column(name = "group_created_count", nullable = false) private int groupCreatedCnt;
78-118: 카운트 증가/감소 메서드는 도메인 인바리언트를 잘 지키고 있지만, 예외 상황 처리 방식은 팀 컨벤션에 맞춰 볼 여지가 있습니다.0 이하로 내려가지 않도록
> 0가드로 막은 건 좋습니다만, “0인데 감소를 시도한 상황”이 로직 버그일 수도 있어 단순 무시 대신 로그/예외/어설션 등으로 드러내고 싶은지 한 번 논의해 볼 만합니다. 또한 네 종류 카운트에 대한 패턴이 완전히 동일하니, 필요하다면 private 헬퍼로 중복을 줄이는 것도 선택지입니다.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (13)
src/main/java/team/wego/wegobackend/auth/application/AuthService.java(1 hunks)src/main/java/team/wego/wegobackend/auth/application/dto/request/SignupRequest.java(0 hunks)src/main/java/team/wego/wegobackend/auth/application/dto/response/SignupResponse.java(0 hunks)src/main/java/team/wego/wegobackend/common/security/SecurityConfig.java(0 hunks)src/main/java/team/wego/wegobackend/common/security/SecurityEndpoints.java(1 hunks)src/main/java/team/wego/wegobackend/user/application/UserService.java(1 hunks)src/main/java/team/wego/wegobackend/user/application/dto/response/UserInfoResponse.java(1 hunks)src/main/java/team/wego/wegobackend/user/domain/User.java(1 hunks)src/main/java/team/wego/wegobackend/user/exception/UserNotFoundException.java(1 hunks)src/main/java/team/wego/wegobackend/user/presentation/UserController.java(2 hunks)src/main/resources/application.yml(1 hunks)src/test/http/auth/auth-api.http(1 hunks)src/test/http/user/user-api.http(1 hunks)
💤 Files with no reviewable changes (3)
- src/main/java/team/wego/wegobackend/common/security/SecurityConfig.java
- src/main/java/team/wego/wegobackend/auth/application/dto/response/SignupResponse.java
- src/main/java/team/wego/wegobackend/auth/application/dto/request/SignupRequest.java
🧰 Additional context used
🧬 Code graph analysis (2)
src/main/java/team/wego/wegobackend/user/presentation/UserController.java (1)
src/main/java/team/wego/wegobackend/user/application/UserService.java (1)
Slf4j(11-27)
src/main/java/team/wego/wegobackend/user/application/UserService.java (3)
src/main/java/team/wego/wegobackend/user/presentation/UserController.java (1)
Slf4j(18-49)src/main/java/team/wego/wegobackend/common/security/CustomUserDetailsService.java (1)
Service(15-28)src/main/java/team/wego/wegobackend/user/exception/UserNotFoundException.java (1)
UserNotFoundException(6-11)
🔇 Additional comments (8)
src/main/java/team/wego/wegobackend/auth/application/AuthService.java (1)
48-51: LGTM! phoneNumber 제거가 올바르게 반영되었습니다.User 빌더에서 phoneNumber 필드가 제거되었고, 기본 역할(ROLE_USER)이 정상적으로 설정됩니다.
src/test/http/auth/auth-api.http (1)
5-9: LGTM! 테스트 요청이 변경된 API 스펙과 일치합니다.회원가입 요청에서 phoneNumber 필드가 제거되어 SignupRequest DTO와 일관성이 유지됩니다.
src/main/java/team/wego/wegobackend/user/exception/UserNotFoundException.java (1)
6-10: LGTM! 예외 클래스가 표준 패턴을 따릅니다.AppException을 상속하고 적절한 에러 코드를 사용하는 표준 예외 구현입니다.
src/main/java/team/wego/wegobackend/user/application/dto/response/UserInfoResponse.java (2)
30-36: LGTM! 카운트 필드가 추가되었습니다.새로운 카운트 필드들(
followeesCnt,followersCnt,groupJoinedCnt,groupCreatedCnt)이 비정규화 요구사항에 맞게 추가되었습니다. primitiveint타입 사용은 null이 발생하지 않는 카운트 값에 적합합니다.
50-53: LGTM! from() 매핑이 올바르게 업데이트되었습니다.User 도메인의 새로운 카운트 필드들이 UserInfoResponse로 정확하게 매핑됩니다.
src/main/java/team/wego/wegobackend/user/application/UserService.java (1)
18-26: Count update methods are already implemented in the User domain.All four count update methods exist in User.java:
increaseFolloweeCount()/decreaseFolloweeCount()(lines 80-88)increaseFollowerCount()/decreaseFollowerCount()(lines 90-98)increaseGroupJoinedCount()/decreaseGroupJoinedCount()(lines 100-108)increaseGroupCreatedCount()/decreaseGroupCreatedCount()(lines 110-118)Each method includes proper null-safety checks before decrementing. The count fields are correctly mapped to UserInfoResponse through the
from()builder.Likely an incorrect or invalid review comment.
src/main/java/team/wego/wegobackend/user/presentation/UserController.java (1)
40-48: Authentication is already enforced—this review comment is incorrect.The
/api/v1/user/**endpoints are protected by default through the security configuration. TheSecurityConfigrequires authentication for all requests except those matchingPUBLIC_PATTERNS(auth, docs, health, etc.), and/api/v1/user/**is not in that list. TheJwtAuthenticationFiltervalidates JWT tokens for all user endpoints before they are executed.If there is a business logic concern about whether any authenticated user should be able to view any other user's profile, that is a separate authorization issue—not an authentication validation issue. The current implementation correctly enforces that authentication is required; it simply permits all authenticated users to view profiles without ownership or role-based restrictions.
Likely an incorrect or invalid review comment.
src/main/java/team/wego/wegobackend/user/domain/User.java (1)
71-71: 생성자(빌더)에서Role기본값을ROLE_USER로 처리한 부분은 도메인 관점에서 자연스럽습니다.
phoneNumber제거 후에도 필수 필드들만 받아서 생성하고,role == null일 때 기본값을 지정하는 패턴이 명확합니다. 이 생성자에만@Builder가 붙어 있으니, 빌더/생성자 사용처가 모두 이 네 파라미터 시그니처에 맞게 정리되었는지만 한 번 더 확인해 주세요.
| "/api/v*/auth/**", | ||
| "/api/v*/docs/**", | ||
| "/api/v*/health", | ||
| "/h2-console/**", |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's understand the file structure and locate the relevant files
echo "=== Repository structure ==="
git ls-files | head -20
echo -e "\n=== Locating SecurityEndpoints.java ==="
find . -name "SecurityEndpoints.java" -type f
echo -e "\n=== Locating SecurityConfig.java ==="
find . -name "SecurityConfig.java" -type f
echo -e "\n=== Application properties and profiles ==="
fd 'application.*\.(properties|yml|yaml)' | head -20Repository: WeGo-Together/WeGo_BackEnd
Length of output: 1048
🏁 Script executed:
echo "=== Reading SecurityEndpoints.java ==="
cat -n ./src/main/java/team/wego/wegobackend/common/security/SecurityEndpoints.java
echo -e "\n=== Reading SecurityConfig.java ==="
cat -n ./src/main/java/team/wego/wegobackend/common/security/SecurityConfig.java
echo -e "\n=== Reading application.yml ==="
cat -n src/main/resources/application.ymlRepository: WeGo-Together/WeGo_BackEnd
Length of output: 5433
🏁 Script executed:
echo "=== Looking for profile-specific application files ==="
fd 'application.*\.(properties|yml|yaml)' --full-path
echo -e "\n=== Checking build.gradle for H2 dependency ==="
cat build.gradle | grep -A5 -B5 'h2\|H2' || echo "No H2 found in grep, checking full file..."
echo -e "\n=== Checking for any h2-console references in codebase ==="
rg -n 'h2-console|h2\.console|spring\.h2' --type=java --type=yml --type=properties
echo -e "\n=== Checking if profiles can be conditionally set ==="
fd 'build.gradle' -exec grep -n 'profile\|spring.profiles' {} \;Repository: WeGo-Together/WeGo_BackEnd
Length of output: 919
🏁 Script executed:
echo "=== Checking for @ConditionalOnProfile or profile-based configurations ==="
rg -n '@ConditionalOnProfile|@Profile' --type=java
echo -e "\n=== Checking for any H2 console property configuration ==="
rg -n 'spring\.h2' --type=java --type=properties
echo -e "\n=== Full build.gradle to see H2 and other dependencies ==="
cat build.gradleRepository: WeGo-Together/WeGo_BackEnd
Length of output: 1854
H2 콘솔 엔드포인트 노출은 의도적인 개발환경 보호가 필요합니다.
/h2-console/**이 PUBLIC_PATTERNS에 조건 없이 노출되어 있습니다. H2 의존성이 developmentOnly로 설정되어 프로덕션 빌드에서는 제외되지만, 보안 설정이 명시적으로 프로필 기반으로 조건화되지 않았습니다.
개선사항:
SecurityConfig에서@ConditionalOnProfile("local")또는 유사한 방식으로 개발환경에서만 h2-console을 노출하도록 설정SecurityEndpoints.PUBLIC_PATTERNS를 프로필 기반으로 분리하거나 동적으로 구성- 또는
application-dev.yml과application-prod.yml등 프로필별 설정 파일 분리
현재 구성은 우연히 작동하지만, 의도적이고 명시적인 개발/프로덕션 분리가 필요합니다.
🤖 Prompt for AI Agents
In src/main/java/team/wego/wegobackend/common/security/SecurityEndpoints.java
around line 9, the H2 console pattern "/h2-console/**" is unconditionally
exposed in PUBLIC_PATTERNS; change this so the H2 endpoint is only enabled in
development/local profiles: remove "/h2-console/**" from the global
PUBLIC_PATTERNS and instead conditionally add it at runtime based on the active
Spring profile (e.g., annotate the SecurityConfig or the bean that builds
PUBLIC_PATTERNS with @Profile("local") or use Environment/ConfigProperties to
append the pattern when profile == "local"), or alternatively drive inclusion
via a boolean property in application-dev.yml/application-prod.yml and read that
property to include the H2 pattern only for dev. Ensure the default
(no-profile/production) configuration does not contain the H2 pattern.
| public UserInfoResponse getProfile(Long userId) { | ||
|
|
||
| User user = userRepository.findById(userId) | ||
| .orElseThrow(UserNotFoundException::new); | ||
|
|
||
| return UserInfoResponse.from(user); | ||
|
|
||
|
|
||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
@transactional(readOnly = true) 어노테이션이 누락되었습니다.
읽기 전용 트랜잭션을 명시하면 성능 최적화와 데이터 정합성을 보장할 수 있습니다.
+ @Transactional(readOnly = true)
public UserInfoResponse getProfile(Long userId) {
User user = userRepository.findById(userId)
.orElseThrow(UserNotFoundException::new);
return UserInfoResponse.from(user);
-
-
}🤖 Prompt for AI Agents
In src/main/java/team/wego/wegobackend/user/application/UserService.java around
lines 18 to 26, the getProfile method is missing a read-only transaction
annotation; add @Transactional(readOnly = true) to the method (or class if
appropriate) and ensure the
org.springframework.transaction.annotation.Transactional import is present so
the method executes within a read-only transaction for performance and
consistency.
| return ResponseEntity | ||
| .status(200) | ||
| .body(ApiResponse.success(200, | ||
| response)); |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
상태 코드 사용이 일관되지 않습니다.
Line 32에서는 HttpStatus.OK를 사용하지만 여기서는 매직 넘버 200을 직접 사용합니다. 일관성을 위해 HttpStatus.OK를 사용하세요.
return ResponseEntity
- .status(200)
- .body(ApiResponse.success(200,
+ .status(HttpStatus.OK)
+ .body(ApiResponse.success(HttpStatus.OK.value(),
response));📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| return ResponseEntity | |
| .status(200) | |
| .body(ApiResponse.success(200, | |
| response)); | |
| return ResponseEntity | |
| .status(HttpStatus.OK) | |
| .body(ApiResponse.success(HttpStatus.OK.value(), | |
| response)); |
🤖 Prompt for AI Agents
In src/main/java/team/wego/wegobackend/user/presentation/UserController.java
around lines 44–47, replace the magic number 200 with the HttpStatus constant:
call ResponseEntity.status(HttpStatus.OK) and pass HttpStatus.OK.value() (or the
HttpStatus.OK enum if ApiResponse supports it) into ApiResponse.success; also
ensure org.springframework.http.HttpStatus is imported if not already. This
keeps status usage consistent with the earlier line that uses HttpStatus.OK.
| profiles: | ||
| active: local |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cd src/main/resources && find . -name "application*.yml" -o -name "application*.properties" && cd ../../../Repository: WeGo-Together/WeGo_BackEnd
Length of output: 87
🏁 Script executed:
git ls-files | grep -E "(application|config|docker|k8s|pipeline|gradle|maven|pom)" | head -30Repository: WeGo-Together/WeGo_BackEnd
Length of output: 2137
🏁 Script executed:
rg -l "SPRING_PROFILES_ACTIVE|spring.profiles.active" --type yaml --type properties --type shRepository: WeGo-Together/WeGo_BackEnd
Length of output: 104
🏁 Script executed:
cat -n src/main/resources/application.ymlRepository: WeGo-Together/WeGo_BackEnd
Length of output: 2573
🏁 Script executed:
cat docker-compose.ymlRepository: WeGo-Together/WeGo_BackEnd
Length of output: 406
🏁 Script executed:
cat build.gradle | head -100Repository: WeGo-Together/WeGo_BackEnd
Length of output: 1607
🏁 Script executed:
find .github/workflows -type f -name "*.yml" -o -name "*.yaml" 2>/dev/nullRepository: WeGo-Together/WeGo_BackEnd
Length of output: 100
🏁 Script executed:
rg "SPRING_PROFILES_ACTIVE" .github/Repository: WeGo-Together/WeGo_BackEnd
Length of output: 52
🏁 Script executed:
cat .github/workflows/workflow.ymlRepository: WeGo-Together/WeGo_BackEnd
Length of output: 1398
Move profile activation to environment variables or deployment configuration.
Hardcoding spring.profiles.active: local in the committed application.yml causes the local profile to activate in production. Your deployment script (.github/workflows/workflow.yml) does not override this via SPRING_PROFILES_ACTIVE environment variable or command-line arguments, so the local profile will be used in the Lightsail/EC2 environment. This is inconsistent with your approach of externalizing sensitive data through environment variables.
Set the profile via environment variable in your deployment script:
nohup java -jar "$JAR" --server.port="$APP_PORT" --spring.profiles.active=prod > ./output.log 2>&1 &Or remove the profile activation from application.yml and configure it per-environment through the deployment pipeline.
🤖 Prompt for AI Agents
In src/main/resources/application.yml around lines 5-6 the spring profile is
hardcoded to "local", which forces the local profile in deployed environments;
remove the hardcoded profiles.active entry from application.yml (or replace it
with a non-committed placeholder) and instead set SPRING_PROFILES_ACTIVE in your
deployment pipeline or pass --spring.profiles.active at JVM startup (update
.github/workflows/workflow.yml / your start script to export the env var or
include the CLI arg for prod).
📝 Pull Request
📌 PR 종류
해당하는 항목에 체크해주세요.
✨ 변경 내용
유저 정보 조회를 위한 엔드포인트 개발
🔍 관련 이슈
해당 PR이 해결하는 이슈가 있다면 연결해주세요.
Closes #49
🧪 테스트
변경된 기능에 대한 테스트 범위 또는 테스트 결과를 작성해주세요.
🚨 확인해야 할 사항 (Checklist)
PR을 제출하기 전에 아래 항목들을 확인해주세요.
🙋 기타 참고 사항
User 도메인 비정규화 컬럼 추가되었습니다. (+ cnt 업데이트 메서드 추가)
Summary by CodeRabbit
릴리스 노트
New Features
Chores
✏️ Tip: You can customize this high-level summary in your review settings.