Conversation
- 차단 기능 rebase 및 충돌 해결
- University -> HostUniversity 변경
Walkthrough새로운 관리자 사용자 관리 기능을 구현하는 변경사항입니다. 다음의 계층별 구성요소들이 추가되었습니다:
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Tip Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (8)
src/main/java/com/example/solidconnection/admin/dto/RestrictedUserSearchResponse.java (1)
6-12:reportedInfoResponse의null가능성을 타입 수준에서 표현해 주세요.
- 현재 상태
BannedInfoResponse는isBanned = false형태로 항상 존재할 수 있지만,reportedInfoResponse는 차단만 된 사용자(신고 이력 없음)에게null일 수 있습니다.- 제안 사항
- 두 필드에
@Nullable어노테이션으로 의도를 명시하면, 서비스·리포지터리 레이어에서 null 처리를 명확히 할 수 있습니다:+import org.springframework.lang.Nullable; public record RestrictedUserSearchResponse( String nickname, Role role, UserStatus userStatus, + `@Nullable` ReportedInfoResponse reportedInfoResponse, + `@Nullable` BannedInfoResponse bannedInfoResponse ) {}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/example/solidconnection/admin/dto/RestrictedUserSearchResponse.java` around lines 6 - 12, The record component reportedInfoResponse in RestrictedUserSearchResponse can be null but this isn't expressed in the type; annotate the component with a `@Nullable` annotation (e.g., org.springframework.lang.Nullable or javax.annotation.Nullable) on the reportedInfoResponse component in the RestrictedUserSearchResponse record declaration so callers and static tools know it may be null, and add the corresponding import; leave BannedInfoResponse unchanged.src/main/java/com/example/solidconnection/admin/dto/BannedInfoResponse.java (1)
7-7:duration은isBanned = false일 때 의미가 없는 필드입니다.
- 현재 상태
- 차단되지 않은 경우(
isBanned = false),duration은null이 될 수 있으나 이에 대한 안내가 없습니다.- 제안 사항
- 최소한
@Nullable어노테이션으로 의도를 명시하거나 Javadoc으로 보완하면 소비하는 쪽에서 NPE를 예방할 수 있습니다.+import org.springframework.lang.Nullable; public record BannedInfoResponse( boolean isBanned, + `@Nullable` UserBanDuration duration ) {}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/example/solidconnection/admin/dto/BannedInfoResponse.java` at line 7, The duration field in BannedInfoResponse (UserBanDuration duration) can be null when isBanned == false; annotate the field (and its getter/constructor parameter if present) with a `@Nullable` annotation (e.g., org.jetbrains.annotations.Nullable or javax.annotation.Nullable used in the project) and/or add a one-line Javadoc on the duration field explaining "nullable when isBanned is false" so callers know to null-check; update imports and any serialization/deserialization hints (e.g., Jackson) if needed to preserve nullability.src/main/java/com/example/solidconnection/admin/dto/MentorApplicationHistoryInfoResponse.java (1)
6-10: 기존MentorApplicationHistoryResponse와의 네이밍 유사성을 확인해주세요.기존 코드에 이미
MentorApplicationHistoryResponse가 존재하며, 이 새 레코드는id와applicationOrder를 제외한 부분집합입니다.
- 현재 네이밍:
MentorApplicationHistoryInfoResponse—Info접미사만으로 구분됩니다.- 혼동 가능성: 두 DTO가 유사한 이름을 가져 개발자가 실수로 잘못된 타입을 사용할 수 있습니다.
대안으로
MentorApplicationHistorySummaryResponse같은 이름이 용도 차이를 더 명확히 드러낼 수 있으나, 프로젝트 전체 네이밍 컨벤션에 맞추시는 것이 우선입니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/example/solidconnection/admin/dto/MentorApplicationHistoryInfoResponse.java` around lines 6 - 10, Rename the DTO class MentorApplicationHistoryInfoResponse to a clearer, non-ambiguous name (e.g., MentorApplicationHistorySummaryResponse) to avoid confusion with the existing MentorApplicationHistoryResponse; update all references/usages of MentorApplicationHistoryInfoResponse (constructors, imports, tests, serializers) accordingly and ensure the new name matches the project's DTO naming conventions.src/main/java/com/example/solidconnection/admin/dto/UserInfoDetailResponse.java (1)
5-9: Nullable 필드에 대한@Nullable어노테이션 추가를 권장드립니다.현재 주석으로 null 가능성을 잘 설명해주셨지만,
@Nullable어노테이션을 함께 사용하면 다음과 같은 장점이 있습니다:
- 정적 분석 도구가 null 안전성을 자동으로 검증할 수 있습니다.
- API 문서 생성 시 nullable 여부가 자동 반영됩니다.
- JSON 직렬화 라이브러리(Jackson 등)와의 연동에도 명확한 시그널을 줍니다.
♻️ 참고 예시
+import org.springframework.lang.Nullable; + public record UserInfoDetailResponse( - MentorInfoResponse mentorInfoResponse, // 멘티일 경우 null - MenteeInfoResponse menteeInfoResponse, // 멘토일 경우 null + `@Nullable` MentorInfoResponse mentorInfoResponse, // 멘티일 경우 null + `@Nullable` MenteeInfoResponse menteeInfoResponse, // 멘토일 경우 null List<ReportedHistoryResponse> reportedHistoryResponses ) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/example/solidconnection/admin/dto/UserInfoDetailResponse.java` around lines 5 - 9, Add nullable annotations to the record components in UserInfoDetailResponse: annotate MentorInfoResponse mentorInfoResponse and MenteeInfoResponse menteeInfoResponse (and optionally List<ReportedHistoryResponse> reportedHistoryResponses if it can be null) with `@Nullable` (e.g., org.springframework.lang.Nullable) and add the corresponding import; this makes nullability explicit for static analysis, docs and serializers while leaving the record signature otherwise unchanged.src/main/java/com/example/solidconnection/admin/service/AdminUserService.java (1)
38-50:siteUser.getId()대신userId를 바로 전달해도 됩니다.
findById(userId)로 존재 여부를 검증하는 것은 좋습니다.- 하지만 검증 후
siteUser.getId()를 호출하는 것은 입력받은userId와 동일한 값이므로 불필요한 참조입니다.userId를 직접 전달하면 의도가 더 명확해집니다.♻️ 제안된 수정
`@Transactional`(readOnly = true) public UserInfoDetailResponse getUserInfoDetail(long userId) { - SiteUser siteUser = siteUserRepository.findById(userId) + siteUserRepository.findById(userId) .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); - return siteUserFilterRepository.getUserInfoDetailByUserId(siteUser.getId()); + return siteUserFilterRepository.getUserInfoDetailByUserId(userId); } `@Transactional`(readOnly = true) public RestrictedUserInfoDetailResponse getRestrictedUserInfoDetail(long userId) { - SiteUser siteUser = siteUserRepository.findById(userId) + siteUserRepository.findById(userId) .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); - return siteUserFilterRepository.getRestrictedUserInfoDetail(siteUser.getId()); + return siteUserFilterRepository.getRestrictedUserInfoDetail(userId); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/example/solidconnection/admin/service/AdminUserService.java` around lines 38 - 50, The existence check using siteUserRepository.findById(userId) is fine but calling siteUser.getId() afterward is redundant; in both getUserInfoDetail and getRestrictedUserInfoDetail replace the siteUser.getId() argument passed to siteUserFilterRepository.getUserInfoDetailByUserId and getRestrictedUserInfoDetail respectively with the original userId parameter to make the intent clearer and avoid unnecessary object access while keeping the same validation via siteUserRepository.src/test/java/com/example/solidconnection/admin/service/AdminUserServiceTest.java (1)
276-277:@DisplayName어노테이션이 일부@Nested클래스에 누락되어 있습니다.
전체_유저_검색(Line 93)과유저_상세_정보_조회(Line 178) 클래스에는@DisplayName이 있습니다.- 그런데
신고_차단된_유저_검색(Line 277)과신고_차단된_유저_상세_정보_조회(Line 362)에는 빠져 있어 테스트 리포트에서 일관성이 깨집니다.Also applies to: 361-362
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/test/java/com/example/solidconnection/admin/service/AdminUserServiceTest.java` around lines 276 - 277, Add missing `@DisplayName` annotations to the nested test classes 신고_차단된_유저_검색 and 신고_차단된_유저_상세_정보_조회 so test reports match the style used for 전체_유저_검색 and 유저_상세_정보_조회; locate the `@Nested` class declarations for 신고_차단된_유저_검색 and 신고_차단된_유저_상세_정보_조회 and add a `@DisplayName` with an appropriate Korean description (e.g., matching the wording pattern used in 전체_유저_검색 / 유저_상세_정보_조회) directly above each class declaration.src/main/java/com/example/solidconnection/siteuser/repository/custom/SiteUserFilterRepositoryImpl.java (1)
290-316: 멘토 상세 조회 시 여러 번의 개별 쿼리가 실행됩니다.
fetchMentorInfo내에서 멘토 ID 조회 → 멘티 정보 조회 → 멘토 신청 이력 조회로 최대 3회의 쿼리가 실행됩니다.fetchMenteeInfo도 유사하게 여러 쿼리를 수행합니다.- 단일 유저 상세 조회이므로 현재로서는 큰 성능 이슈는 아니지만, 향후 배치 조회 등으로 확장될 경우 단일 쿼리로 최적화하는 것을 고려해 주세요.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/example/solidconnection/siteuser/repository/custom/SiteUserFilterRepositoryImpl.java` around lines 290 - 316, fetchMentorInfo currently issues up to three separate queries (select mentor.id, fetch menteeInfos, fetch mentorApplicationHistory); consolidate into a single query to avoid N+1/extra roundtrips by using left joins or subqueries in one query with queryFactory: join siteUser -> leftJoin(mentor) -> leftJoin(mentoring).on(mentoring.mentorId.eq(mentor.id)) and leftJoin(mentorApplication).on(mentorApplication.siteUserId.eq(siteUser.id)), select and map MATCHED_INFO_RESPONSE_PROJECTION and MENTOR_APPLICATION_HISTORY_RESPONSE_PROJECTION (or use grouping/transformers) so MentorInfoResponse is constructed from a single fetch; apply the same approach to fetchMenteeInfo to eliminate its multiple queries.src/main/java/com/example/solidconnection/siteuser/repository/custom/SiteUserFilterRepository.java (1)
1-21:siteuser.repository패키지에서admin.dto에 직접 의존하고 있습니다.
SiteUserFilterRepository는 도메인/인프라 계층에 위치하지만, 반환 타입이 모두admin.dto패키지의 DTO입니다.- 이렇게 되면 도메인 레이어가 특정 표현 계층(admin)에 결합됩니다.
- 당장은 문제가 없지만, 추후 다른 컨텍스트에서 동일한 쿼리를 재사용해야 할 때 리팩토링이 필요해질 수 있습니다.
- 대안으로, 이 인터페이스와 구현체를
admin패키지 하위로 이동하거나, 도메인 중립적인 projection을 반환한 뒤 서비스 계층에서 변환하는 방법이 있습니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/example/solidconnection/siteuser/repository/custom/SiteUserFilterRepository.java` around lines 1 - 21, SiteUserFilterRepository currently depends on admin.dto types (UserSearchResponse, RestrictedUserSearchResponse, UserInfoDetailResponse, RestrictedUserInfoDetailResponse) which couples the domain/infra layer to the admin presentation layer; either move the interface and its implementation into the admin package (so symbols SiteUserFilterRepository and its methods searchAllUsers, searchRestrictedUsers, getUserInfoDetailByUserId, getRestrictedUserInfoDetail live under com.example.solidconnection.admin.*) or change the repository API to return domain-neutral projections/DTOs (e.g., UserSearchProjection, RestrictedUserSearchProjection, UserInfoProjection) defined in a domain- or repository-level package and perform mapping to admin.dto types in the admin service layer; update the implementation and callers accordingly to use the chosen projection types or new package location.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/main/java/com/example/solidconnection/admin/dto/BannedInfoResponse.java`:
- Around line 5-8: The boolean record component is serialized as "banned"
because Jackson strips the "is" prefix; update the BannedInfoResponse record to
explicitly name the JSON property by annotating the boolean component with
`@JsonProperty`("isBanned") (i.e., add import
com.fasterxml.jackson.annotation.JsonProperty and annotate the component
declaration `@JsonProperty("isBanned") boolean isBanned`) so the generated JSON
key matches the expected "isBanned".
In `@src/main/java/com/example/solidconnection/admin/dto/MenteeInfoResponse.java`:
- Around line 5-8: Rename the record component univApplyInfos to univApplyInfo
in the MenteeInfoResponse record to match its single-object type
UnivApplyInfoResponse (leave mentorInfos as-is); update all references/usages of
MenteeInfoResponse.univApplyInfos to MenteeInfoResponse.univApplyInfo and adjust
any constructors, mappers or serializers that reference the old name.
In
`@src/main/java/com/example/solidconnection/siteuser/repository/custom/SiteUserFilterRepositoryImpl.java`:
- Around line 75-83: RESTRICTED_USER_SEARCH_RESPONSE_PROJECTION currently omits
the user ID; add siteUser.id to the Projections.constructor argument list
(matching the same position/ordering used in USER_SEARCH_RESPONSE_PROJECTION) so
RestrictedUserSearchResponse receives the ID for links to /restricted/{user-id};
if necessary, update RestrictedUserSearchResponse's constructor to accept the id
parameter to match the new projection signature.
- Around line 68-73: BANNED_INFO_RESPONSE_PROJECTION currently mixes
siteUser.userStatus and userBan.duration which can produce inconsistent results;
change the projection so both fields are derived from the userBan join (e.g.,
compute "isBanned" from whether userBan is present/active and use
userBan.duration for duration) and update the corresponding
projection/constructor usage for BannedInfoResponse; apply the same fix to the
other projection(s) around the block that constructs BannedInfoResponse (the
later projection in the file) so both status and duration come from userBan
rather than siteUser.userStatus.
- Around line 240-262: Change getUserInfoDetailByUserId to accept a SiteUser
instead of re-querying: update the interface signature to UserInfoDetailResponse
getUserInfoDetailByUserId(SiteUser user) and in SiteUserFilterRepositoryImpl
remove the queryFactory.selectFrom(...).fetchOne() call; use the passed-in
SiteUser (user) for status/role checks and call
fetchReportedHistories(user.getId()), fetchMentorInfo(user.getId()), and
fetchMenteeInfo(user.getId()) as before. Also update all callers to pass the
already-loaded SiteUser (or, if callers may pass null, validate the parameter at
the start of getUserInfoDetailByUserId and throw a meaningful exception). Ensure
you keep references to UserInfoDetailResponse, fetchReportedHistories,
fetchMentorInfo, fetchMenteeInfo, and SiteUser to locate the changes.
- Around line 51-58: The UserSearchResponse DTO is missing the user ID needed
for navigation; update the UserSearchResponse record/class to include an id
field (matching the type used by siteUser.getId()), then update the
USER_SEARCH_RESPONSE_PROJECTION constant (the Projections.constructor call in
SiteUserFilterRepositoryImpl) to include siteUser.id as the first/appropriate
constructor argument so the projection parameters and the DTO constructor
parameter order match.
In
`@src/test/java/com/example/solidconnection/admin/service/AdminUserServiceTest.java`:
- Line 5: In AdminUserServiceTest replace the incorrect import for
assertThatCode (currently from org.assertj.core.api.AssertionsForClassTypes)
with the standard AssertJ import org.assertj.core.api.Assertions so the test
uses Assertions.assertThatCode; update the import line where assertThatCode is
referenced and run the tests to verify compilation.
---
Nitpick comments:
In `@src/main/java/com/example/solidconnection/admin/dto/BannedInfoResponse.java`:
- Line 7: The duration field in BannedInfoResponse (UserBanDuration duration)
can be null when isBanned == false; annotate the field (and its
getter/constructor parameter if present) with a `@Nullable` annotation (e.g.,
org.jetbrains.annotations.Nullable or javax.annotation.Nullable used in the
project) and/or add a one-line Javadoc on the duration field explaining
"nullable when isBanned is false" so callers know to null-check; update imports
and any serialization/deserialization hints (e.g., Jackson) if needed to
preserve nullability.
In
`@src/main/java/com/example/solidconnection/admin/dto/MentorApplicationHistoryInfoResponse.java`:
- Around line 6-10: Rename the DTO class MentorApplicationHistoryInfoResponse to
a clearer, non-ambiguous name (e.g., MentorApplicationHistorySummaryResponse) to
avoid confusion with the existing MentorApplicationHistoryResponse; update all
references/usages of MentorApplicationHistoryInfoResponse (constructors,
imports, tests, serializers) accordingly and ensure the new name matches the
project's DTO naming conventions.
In
`@src/main/java/com/example/solidconnection/admin/dto/RestrictedUserSearchResponse.java`:
- Around line 6-12: The record component reportedInfoResponse in
RestrictedUserSearchResponse can be null but this isn't expressed in the type;
annotate the component with a `@Nullable` annotation (e.g.,
org.springframework.lang.Nullable or javax.annotation.Nullable) on the
reportedInfoResponse component in the RestrictedUserSearchResponse record
declaration so callers and static tools know it may be null, and add the
corresponding import; leave BannedInfoResponse unchanged.
In
`@src/main/java/com/example/solidconnection/admin/dto/UserInfoDetailResponse.java`:
- Around line 5-9: Add nullable annotations to the record components in
UserInfoDetailResponse: annotate MentorInfoResponse mentorInfoResponse and
MenteeInfoResponse menteeInfoResponse (and optionally
List<ReportedHistoryResponse> reportedHistoryResponses if it can be null) with
`@Nullable` (e.g., org.springframework.lang.Nullable) and add the corresponding
import; this makes nullability explicit for static analysis, docs and
serializers while leaving the record signature otherwise unchanged.
In
`@src/main/java/com/example/solidconnection/admin/service/AdminUserService.java`:
- Around line 38-50: The existence check using
siteUserRepository.findById(userId) is fine but calling siteUser.getId()
afterward is redundant; in both getUserInfoDetail and
getRestrictedUserInfoDetail replace the siteUser.getId() argument passed to
siteUserFilterRepository.getUserInfoDetailByUserId and
getRestrictedUserInfoDetail respectively with the original userId parameter to
make the intent clearer and avoid unnecessary object access while keeping the
same validation via siteUserRepository.
In
`@src/main/java/com/example/solidconnection/siteuser/repository/custom/SiteUserFilterRepository.java`:
- Around line 1-21: SiteUserFilterRepository currently depends on admin.dto
types (UserSearchResponse, RestrictedUserSearchResponse, UserInfoDetailResponse,
RestrictedUserInfoDetailResponse) which couples the domain/infra layer to the
admin presentation layer; either move the interface and its implementation into
the admin package (so symbols SiteUserFilterRepository and its methods
searchAllUsers, searchRestrictedUsers, getUserInfoDetailByUserId,
getRestrictedUserInfoDetail live under com.example.solidconnection.admin.*) or
change the repository API to return domain-neutral projections/DTOs (e.g.,
UserSearchProjection, RestrictedUserSearchProjection, UserInfoProjection)
defined in a domain- or repository-level package and perform mapping to
admin.dto types in the admin service layer; update the implementation and
callers accordingly to use the chosen projection types or new package location.
In
`@src/main/java/com/example/solidconnection/siteuser/repository/custom/SiteUserFilterRepositoryImpl.java`:
- Around line 290-316: fetchMentorInfo currently issues up to three separate
queries (select mentor.id, fetch menteeInfos, fetch mentorApplicationHistory);
consolidate into a single query to avoid N+1/extra roundtrips by using left
joins or subqueries in one query with queryFactory: join siteUser ->
leftJoin(mentor) -> leftJoin(mentoring).on(mentoring.mentorId.eq(mentor.id)) and
leftJoin(mentorApplication).on(mentorApplication.siteUserId.eq(siteUser.id)),
select and map MATCHED_INFO_RESPONSE_PROJECTION and
MENTOR_APPLICATION_HISTORY_RESPONSE_PROJECTION (or use grouping/transformers) so
MentorInfoResponse is constructed from a single fetch; apply the same approach
to fetchMenteeInfo to eliminate its multiple queries.
In
`@src/test/java/com/example/solidconnection/admin/service/AdminUserServiceTest.java`:
- Around line 276-277: Add missing `@DisplayName` annotations to the nested test
classes 신고_차단된_유저_검색 and 신고_차단된_유저_상세_정보_조회 so test reports match the style used
for 전체_유저_검색 and 유저_상세_정보_조회; locate the `@Nested` class declarations for
신고_차단된_유저_검색 and 신고_차단된_유저_상세_정보_조회 and add a `@DisplayName` with an appropriate
Korean description (e.g., matching the wording pattern used in 전체_유저_검색 /
유저_상세_정보_조회) directly above each class declaration.
| public record BannedInfoResponse( | ||
| boolean isBanned, | ||
| UserBanDuration duration | ||
| ) { |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find project configuration files to determine Jackson version
fd -t f '(pom\.xml|build\.gradle|build\.gradle\.kts)' . | head -5Repository: solid-connection/solid-connect-server
Length of output: 95
🏁 Script executed:
#!/bin/bash
# Check Jackson version from Maven config
if [ -f "pom.xml" ]; then
grep -A 3 "jackson" pom.xml | head -20
fiRepository: solid-connection/solid-connect-server
Length of output: 63
🏁 Script executed:
#!/bin/bash
# Locate and examine BannedInfoResponse.java
find . -name "BannedInfoResponse.java" -type fRepository: solid-connection/solid-connect-server
Length of output: 158
🏁 Script executed:
#!/bin/bash
# Search for usages of BannedInfoResponse to understand the impact
rg "BannedInfoResponse" -t java -B 2 -A 2Repository: solid-connection/solid-connect-server
Length of output: 3009
🏁 Script executed:
#!/bin/bash
# Check for similar record definitions and Jackson annotations in the codebase
rg "public record" -t java -A 3 | head -50Repository: solid-connection/solid-connect-server
Length of output: 4166
🏁 Script executed:
#!/bin/bash
# Look for Jackson configuration or custom property naming patterns
rg "@JsonProperty|@JsonNaming|@JsonSerialize" -t java | head -20Repository: solid-connection/solid-connect-server
Length of output: 2253
🏁 Script executed:
#!/bin/bash
# Check build.gradle for Jackson version
cat build.gradle | grep -A 2 -B 2 "jackson" | head -30Repository: solid-connection/solid-connect-server
Length of output: 63
🏁 Script executed:
#!/bin/bash
# Read the actual BannedInfoResponse.java file
cat -n ./src/main/java/com/example/solidconnection/admin/dto/BannedInfoResponse.javaRepository: solid-connection/solid-connect-server
Length of output: 353
🏁 Script executed:
#!/bin/bash
# Check RestrictedUserSearchResponse to see the full context
cat -n ./src/main/java/com/example/solidconnection/admin/dto/RestrictedUserSearchResponse.javaRepository: solid-connection/solid-connect-server
Length of output: 535
🏁 Script executed:
#!/bin/bash
# Look for any test files related to BannedInfoResponse
find . -name "*Test*.java" -type f | xargs grep -l "BannedInfoResponse" 2>/dev/nullRepository: solid-connection/solid-connect-server
Length of output: 65
🏁 Script executed:
#!/bin/bash
# Check the SiteUserFilterRepositoryImpl to see how BannedInfoResponse is constructed
cat -n ./src/main/java/com/example/solidconnection/siteuser/repository/custom/SiteUserFilterRepositoryImpl.java | grep -A 10 "BANNED_INFO_RESPONSE_PROJECTION"Repository: solid-connection/solid-connect-server
Length of output: 1253
🏁 Script executed:
#!/bin/bash
# More comprehensive search for Jackson dependencies
cat build.gradleRepository: solid-connection/solid-connect-server
Length of output: 3198
🏁 Script executed:
#!/bin/bash
# Search for Spring Boot version which bundles Jackson
rg "org.springframework.boot|jackson-databind|jackson-core" build.gradle -A 1Repository: solid-connection/solid-connect-server
Length of output: 823
🏁 Script executed:
#!/bin/bash
# Check if there are any serialization tests
rg "RestrictedUserSearchResponse|BannedInfoResponse" -t java --type-add "test:src/test/**" -B 2 -A 5 | head -50Repository: solid-connection/solid-connect-server
Length of output: 6151
🌐 Web query:
Jackson 2.15 Java record boolean is prefix serialization property naming
💡 Result:
Jackson (including 2.15) derives JSON property names using JavaBean rules. For a boolean accessor named isXxx(), the logical property name becomes xxx (the is prefix is stripped), so a record component like boolean isActive typically serializes as "active": true, not "isActive": true. [1]
How to keep the is in the JSON name (recommended): annotate the record component
public record User(`@com.fasterxml.jackson.annotation.JsonProperty`("isActive")
boolean isActive) {}@JsonProperty explicitly sets the external (JSON) name. [2][3]
Alternative approaches
- Rename the component to
activeand (if you really need the JSON key to beisActive) expose it with@JsonProperty("isActive")on the component. [2][3] - If you need this globally, use a custom naming strategy (more invasive) that preserves
is*names for booleans. [1]
Sources
[1] javathinking.com — explanation of Jackson stripping is for primitive boolean properties and options to prevent it
[2] Jackson Annotations wiki (FasterXML) — @JsonProperty purpose (define external/property name)
[3] @JsonProperty Javadoc — defines the logical/external property name used in JSON
🌐 Web query:
Spring Boot 3.1.5 Jackson version record support is prefix handling
💡 Result:
-
Spring Boot 3.1.5 ships with Jackson 2.15.3 (via dependency management / Jackson BOM). [1]
-
Records are supported by Jackson (databind) and Jackson’s record introspection also recognizes regular getters and
is*getters. That means a record component likeboolean isActivegenerates an accessorisActive()which Jackson can treat as an “is-getter” and apply standard bean name mangling (strip theis), producing JSON like{"active": true}instead of{"isActive": true}. [2][3][4]
How to keep the is prefix (common options):
- Rename the component to avoid the
isprefix (e.g.,active). - Force the JSON name explicitly:
public record MyDto(`@com.fasterxml.jackson.annotation.JsonProperty`("isActive") boolean isActive) {}- As a global behavior change, disable “is-getter” auto-detection (can have side effects for normal POJOs too): Jackson uses
AUTO_DETECT_IS_GETTERSfor this. [3]
Sources:
[1] Spring Boot 3.1.5 dependency versions (Jackson 2.15.3) (docs.spring.io)
[2] Jackson DefaultAccessorNamingStrategy.RecordNaming (records also recognize is-getters) (javadoc.io)
[3] Jackson MapperFeature.AUTO_DETECT_IS_GETTERS (fasterxml.github.io)
[4] Jackson default naming removes get/set/is prefixes (javadoc.io)
Citations:
- 1: https://docs.spring.io/spring-boot/docs/3.1.5/reference/html/dependency-versions.html?utm_source=openai
- 2: https://javadoc.io/static/com.fasterxml.jackson.core/jackson-databind/2.17.3/com/fasterxml/jackson/databind/introspect/DefaultAccessorNamingStrategy.RecordNaming.html?utm_source=openai
- 3: https://fasterxml.github.io/jackson-databind/javadoc/2.12/com/fasterxml/jackson/databind/MapperFeature.html?utm_source=openai
- 4: https://www.javadoc.io/static/com.fasterxml.jackson.core/jackson-databind/2.12.0/com/fasterxml/jackson/databind/PropertyNamingStrategy.html?utm_source=openai
🏁 Script executed:
#!/bin/bash
# Check if there are any API controller tests that validate JSON response
find . -name "*ControllerTest*.java" -o -name "*RestTest*.java" | xargs grep -l "BannedInfoResponse\|RestrictedUserSearchResponse" 2>/dev/null | head -5Repository: solid-connection/solid-connect-server
Length of output: 63
🏁 Script executed:
#!/bin/bash
# Search for any controller endpoint returning RestrictedUserSearchResponse
rg "RestrictedUserSearchResponse" -t java -B 5 -A 5 | grep -E "@PostMapping|@GetMapping|@RequestMapping|public.*RestrictedUserSearchResponse" -A 2 -B 2 | head -40Repository: solid-connection/solid-connect-server
Length of output: 3218
🏁 Script executed:
#!/bin/bash
# Check for any explicit Jackson configuration
rg "ObjectMapper|@Configuration|Jackson|PropertyNamingStrategy" -t java | head -20Repository: solid-connection/solid-connect-server
Length of output: 2601
isBanned 컴포넌트가 Jackson 직렬화 시 "banned"으로 출력됩니다.
Spring Boot 3.1.5의 Jackson 2.15.3는 JavaBean 명명 규칙을 따르며, 불린 타입 접근자(getter)에서 is 접두사를 제거합니다. 따라서 isBanned() 메서드는 JSON에서 "banned"로 직렬화됩니다. 프론트엔드가 "isBanned"를 기대한다면 API 계약이 깨집니다.
다음 중 하나를 선택하여 수정하세요:
-
컴포넌트명 변경 (명시적 이름 변경)
-boolean isBanned, +boolean banned,
-
@JsonProperty사용 (권장 - 기존 코드베이스 패턴과 일치)+import com.fasterxml.jackson.annotation.JsonProperty; public record BannedInfoResponse( - boolean isBanned, + `@JsonProperty`("isBanned") boolean isBanned, UserBanDuration duration ) {}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/com/example/solidconnection/admin/dto/BannedInfoResponse.java`
around lines 5 - 8, The boolean record component is serialized as "banned"
because Jackson strips the "is" prefix; update the BannedInfoResponse record to
explicitly name the JSON property by annotating the boolean component with
`@JsonProperty`("isBanned") (i.e., add import
com.fasterxml.jackson.annotation.JsonProperty and annotate the component
declaration `@JsonProperty("isBanned") boolean isBanned`) so the generated JSON
key matches the expected "isBanned".
| public record MenteeInfoResponse( | ||
| UnivApplyInfoResponse univApplyInfos, | ||
| List<MatchedInfoResponse> mentorInfos | ||
| ) { |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find and examine UnivApplyInfoResponse.java
fd "UnivApplyInfoResponse.java" --type f --exec cat -n {} \;Repository: solid-connection/solid-connect-server
Length of output: 2255
필드명과 타입의 단수/복수 네이밍을 통일해 주세요.
univApplyInfos 필드는 복수형 이름을 사용하고 있지만, UnivApplyInfoResponse 타입은 단일 객체입니다. UnivApplyInfoResponse는 첫 번째, 두 번째, 세 번째 대학 지원 정보를 담는 세 개의 문자열 필드만 포함하고 있으므로, 일관성을 위해 다음과 같이 수정을 권장드립니다.
1. `univApplyInfos` → `univApplyInfo`로 단수형으로 변경
- 타입이 단일 객체이므로 단수형 명명이 의도를 더 명확하게 전달합니다.
2. `mentorInfos`는 `List<MatchedInfoResponse>` 타입과 잘 맞으니 그대로 유지하셔도 됩니다. 👍
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/com/example/solidconnection/admin/dto/MenteeInfoResponse.java`
around lines 5 - 8, Rename the record component univApplyInfos to univApplyInfo
in the MenteeInfoResponse record to match its single-object type
UnivApplyInfoResponse (leave mentorInfos as-is); update all references/usages of
MenteeInfoResponse.univApplyInfos to MenteeInfoResponse.univApplyInfo and adjust
any constructors, mappers or serializers that reference the old name.
| private static final ConstructorExpression<UserSearchResponse> USER_SEARCH_RESPONSE_PROJECTION = | ||
| Projections.constructor( | ||
| UserSearchResponse.class, | ||
| siteUser.nickname, | ||
| siteUser.email, | ||
| siteUser.role, | ||
| siteUser.userStatus | ||
| ); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# UserSearchResponse와 RestrictedUserSearchResponse DTO 파일 찾기
echo "=== UserSearchResponse 파일 검색 ==="
fd 'UserSearchResponse' --type f 2>/dev/null | head -20
echo ""
echo "=== RestrictedUserSearchResponse 파일 검색 ==="
fd 'RestrictedUserSearchResponse' --type f 2>/dev/null | head -20Repository: solid-connection/solid-connect-server
Length of output: 405
🏁 Script executed:
#!/bin/bash
# UserSearchResponse 클래스 정의 확인
find . -name 'UserSearchResponse.java' -type f | xargs cat 2>/dev/null
echo ""
echo "=== RestrictedUserSearchResponse 클래스 정의 ==="
find . -name 'RestrictedUserSearchResponse.java' -type f | xargs cat 2>/dev/nullRepository: solid-connection/solid-connect-server
Length of output: 760
🏁 Script executed:
#!/bin/bash
# SiteUserFilterRepositoryImpl 전체 내용 확인
find . -name 'SiteUserFilterRepositoryImpl.java' -type f | xargs cat 2>/dev/nullRepository: solid-connection/solid-connect-server
Length of output: 15239
UserSearchResponse DTO에 유저 ID 필드 추가가 필요합니다.
검색 결과에서 사용자 상세 조회 페이지로 네비게이션하려면 유저 ID가 필수입니다. 현재 프로젝션의 구성을 정리하면 다음과 같습니다:
-
현재 포함 필드: nickname, email, role, userStatus만 포함되어 있어 프론트엔드에서 개별 사용자의 상세 정보 조회 요청 시 식별자가 부족합니다.
-
필요한 수정 사항:
UserSearchResponse 레코드 수정
public record UserSearchResponse( + Long id, String nickname, String email, Role role, UserStatus userStatus ) { } -
프로젝션 업데이트:
USER_SEARCH_RESPONSE_PROJECTION 수정
private static final ConstructorExpression<UserSearchResponse> USER_SEARCH_RESPONSE_PROJECTION = Projections.constructor( UserSearchResponse.class, + siteUser.id, siteUser.nickname, siteUser.email, siteUser.role, siteUser.userStatus );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/main/java/com/example/solidconnection/siteuser/repository/custom/SiteUserFilterRepositoryImpl.java`
around lines 51 - 58, The UserSearchResponse DTO is missing the user ID needed
for navigation; update the UserSearchResponse record/class to include an id
field (matching the type used by siteUser.getId()), then update the
USER_SEARCH_RESPONSE_PROJECTION constant (the Projections.constructor call in
SiteUserFilterRepositoryImpl) to include siteUser.id as the first/appropriate
constructor argument so the projection parameters and the DTO constructor
parameter order match.
| private static final ConstructorExpression<BannedInfoResponse> BANNED_INFO_RESPONSE_PROJECTION = | ||
| Projections.constructor( | ||
| BannedInfoResponse.class, | ||
| siteUser.userStatus.eq(UserStatus.BANNED), | ||
| userBan.duration | ||
| ); |
There was a problem hiding this comment.
BANNED_INFO_RESPONSE_PROJECTION에서 userStatus와 실제 차단 레코드 간 불일치 가능성이 있습니다.
- Line 71:
siteUser.userStatus.eq(UserStatus.BANNED)는 유저 상태 필드 기반으로 "현재 차단 여부"를 판단합니다. - Line 72:
userBan.duration은 left join된 차단 레코드에서 가져옵니다. - 차단 레코드가 만료되었거나 조건에 맞지 않아 join 결과가 없으면,
userStatus는 BANNED이지만duration은null이 될 수 있습니다. - 반대로
userStatus가 아직 BANNED로 업데이트되지 않았는데 차단 레코드가 존재하는 경우도 불일치가 발생할 수 있습니다. - 이 두 값의 소스를 통일하는 것이 안전합니다 (예: 둘 다
userBan레코드 기반으로 판단).
Also applies to: 160-202
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/main/java/com/example/solidconnection/siteuser/repository/custom/SiteUserFilterRepositoryImpl.java`
around lines 68 - 73, BANNED_INFO_RESPONSE_PROJECTION currently mixes
siteUser.userStatus and userBan.duration which can produce inconsistent results;
change the projection so both fields are derived from the userBan join (e.g.,
compute "isBanned" from whether userBan is present/active and use
userBan.duration for duration) and update the corresponding
projection/constructor usage for BannedInfoResponse; apply the same fix to the
other projection(s) around the block that constructs BannedInfoResponse (the
later projection in the file) so both status and duration come from userBan
rather than siteUser.userStatus.
| private static final ConstructorExpression<RestrictedUserSearchResponse> RESTRICTED_USER_SEARCH_RESPONSE_PROJECTION = | ||
| Projections.constructor( | ||
| RestrictedUserSearchResponse.class, | ||
| siteUser.nickname, | ||
| siteUser.role, | ||
| siteUser.userStatus, | ||
| REPORTED_INFO_RESPONSE_PROJECTION, | ||
| BANNED_INFO_RESPONSE_PROJECTION | ||
| ); |
There was a problem hiding this comment.
RESTRICTED_USER_SEARCH_RESPONSE_PROJECTION에도 유저 ID가 빠져 있습니다.
위 USER_SEARCH_RESPONSE_PROJECTION과 동일한 이유로, 제한된 유저 검색 결과에서도 유저 ID가 포함되어야 상세 조회(/restricted/{user-id})로 연결할 수 있습니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/main/java/com/example/solidconnection/siteuser/repository/custom/SiteUserFilterRepositoryImpl.java`
around lines 75 - 83, RESTRICTED_USER_SEARCH_RESPONSE_PROJECTION currently omits
the user ID; add siteUser.id to the Projections.constructor argument list
(matching the same position/ordering used in USER_SEARCH_RESPONSE_PROJECTION) so
RestrictedUserSearchResponse receives the ID for links to /restricted/{user-id};
if necessary, update RestrictedUserSearchResponse's constructor to accept the id
parameter to match the new projection signature.
| @Override | ||
| public UserInfoDetailResponse getUserInfoDetailByUserId(long userId) { | ||
| SiteUser user = queryFactory | ||
| .selectFrom(siteUser) | ||
| .where(siteUser.id.eq(userId)) | ||
| .fetchOne(); | ||
|
|
||
| // 신고 내역 | ||
| List<ReportedHistoryResponse> reportedHistoryResponses = new ArrayList<>(); | ||
| if (user.getUserStatus() != UserStatus.ACTIVE) { | ||
| reportedHistoryResponses = fetchReportedHistories(userId); | ||
| } | ||
|
|
||
| if (user.getRole() == Role.MENTOR) { | ||
| // 멘토 상세 내역 | ||
| MentorInfoResponse mentorInfoResponse = fetchMentorInfo(userId); | ||
| return new UserInfoDetailResponse(mentorInfoResponse, null, reportedHistoryResponses); | ||
| } else { | ||
| // 멘티 상세 내역 | ||
| MenteeInfoResponse menteeInfoResponse = fetchMenteeInfo(userId); | ||
| return new UserInfoDetailResponse(null, menteeInfoResponse, reportedHistoryResponses); | ||
| } | ||
| } |
There was a problem hiding this comment.
getUserInfoDetailByUserId에서 user가 null일 경우 NPE가 발생합니다.
- Line 242-245에서
fetchOne()은 결과가 없으면null을 반환합니다. - Line 249에서
user.getUserStatus()를 호출하므로, 만약 서비스 레이어의 존재 검증과 이 쿼리 사이에 데이터가 삭제되면 NPE가 발생합니다. - 또한 이 쿼리 자체가 서비스에서 이미 수행한
findById와 중복됩니다 — 서비스에서 조회한SiteUser객체를 매개변수로 전달받으면 추가 쿼리 없이 해결됩니다.
♻️ 서비스에서 SiteUser를 전달받는 방식으로 리팩토링
인터페이스 메서드 시그니처 변경:
UserInfoDetailResponse getUserInfoDetailByUserId(SiteUser user);구현체 변경:
`@Override`
-public UserInfoDetailResponse getUserInfoDetailByUserId(long userId) {
- SiteUser user = queryFactory
- .selectFrom(siteUser)
- .where(siteUser.id.eq(userId))
- .fetchOne();
-
+public UserInfoDetailResponse getUserInfoDetailByUserId(SiteUser user) {
+ long userId = user.getId();
+
// 신고 내역
List<ReportedHistoryResponse> reportedHistoryResponses = new ArrayList<>();
if (user.getUserStatus() != UserStatus.ACTIVE) {
reportedHistoryResponses = fetchReportedHistories(userId);
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/main/java/com/example/solidconnection/siteuser/repository/custom/SiteUserFilterRepositoryImpl.java`
around lines 240 - 262, Change getUserInfoDetailByUserId to accept a SiteUser
instead of re-querying: update the interface signature to UserInfoDetailResponse
getUserInfoDetailByUserId(SiteUser user) and in SiteUserFilterRepositoryImpl
remove the queryFactory.selectFrom(...).fetchOne() call; use the passed-in
SiteUser (user) for status/role checks and call
fetchReportedHistories(user.getId()), fetchMentorInfo(user.getId()), and
fetchMenteeInfo(user.getId()) as before. Also update all callers to pass the
already-loaded SiteUser (or, if callers may pass null, validate the parameter at
the start of getUserInfoDetailByUserId and throw a meaningful exception). Ensure
you keep references to UserInfoDetailResponse, fetchReportedHistories,
fetchMentorInfo, fetchMenteeInfo, and SiteUser to locate the changes.
|
|
||
| import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; | ||
| import static org.assertj.core.api.Assertions.assertThat; | ||
| import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Check the file and surrounding imports
head -20 src/test/java/com/example/solidconnection/admin/service/AdminUserServiceTest.javaRepository: solid-connection/solid-connect-server
Length of output: 1317
🏁 Script executed:
# Search for assertThatCode usage in the codebase to see how it's typically imported
rg -n 'assertThatCode' --type=java -B2 -A2Repository: solid-connection/solid-connect-server
Length of output: 50394
🏁 Script executed:
# Also check if AssertionsForClassTypes exists in imports
rg -n 'AssertionsForClassTypes' --type=javaRepository: solid-connection/solid-connect-server
Length of output: 3591
assertThatCode import 경로를 표준 AssertJ API로 수정하세요.
assertThatCode는 org.assertj.core.api.AssertionsForClassTypes가 아닌 org.assertj.core.api.Assertions에서 제공합니다. AssertionsForClassTypes는 클래스 타입 관련 assertion 전용 클래스로, 예외 테스트용 메서드를 포함하지 않습니다.
✅ 권장 import 변경
-import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatCode;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/test/java/com/example/solidconnection/admin/service/AdminUserServiceTest.java`
at line 5, In AdminUserServiceTest replace the incorrect import for
assertThatCode (currently from org.assertj.core.api.AssertionsForClassTypes)
with the standard AssertJ import org.assertj.core.api.Assertions so the test
uses Assertions.assertThatCode; update the import line where assertThatCode is
referenced and run the tests to verify compilation.
관련 이슈
작업 내용
유저 관리 페이지에 필요한 기능 추가
특이 사항
리뷰 요구사항 (선택)