[WTH-196] 인증 정책 수정, 이미지 관련 필드 수정#32
Conversation
# Conflicts: # src/test/kotlin/com/weeth/domain/board/application/usecase/command/ManagePostUseCaseTest.kt # src/test/kotlin/com/weeth/domain/board/application/usecase/query/GetPostQueryServiceTest.kt
|
Caution Review failedPull request was closed or merged during review 📝 WalkthroughWalkthrough사용자 도메인의 Role을 제거하고 Club 도메인의 MemberRole로 전환, ClubMember 기반 권한 검사 도입, 이미지 URL을 저장소 키로 변경, JWT 레이어에서 role 정보 제거 및 관련 매퍼/컨트롤러/리포지토리/테스트를 대규모로 변경함. Changes
Sequence DiagramsequenceDiagram
participant Client
participant Controller
participant UseCase
participant ClubMemberPolicy
participant Repository
participant Domain
Client->>Controller: POST /api/v4/clubs/{clubId}/boards/{boardId}/posts (요청)
Controller->>UseCase: save(clubId, userId, postRequest)
UseCase->>ClubMemberPolicy: getActiveMember(clubId, userId)
ClubMemberPolicy->>Repository: findByClubIdAndUserId(clubId, userId)
Repository-->>ClubMemberPolicy: ClubMember(memberRole=ADMIN|LEAD|USER)
ClubMemberPolicy-->>UseCase: ClubMember
UseCase->>Repository: getBoard(boardId)
Repository-->>UseCase: Board(writePermission=MemberRole.ADMIN)
UseCase->>Domain: canWriteBy(member.memberRole)
Domain-->>UseCase: true/false (isAdminOrLead 허용)
alt Can Write
UseCase->>Repository: save(Post)
Repository-->>UseCase: saved Post
UseCase-->>Controller: PostResponse
Controller-->>Client: 200 OK
else Cannot Write
UseCase-->>Controller: NotAuthorizedWriteException
Controller-->>Client: 403 Forbidden
end
Estimated code review effort🎯 5 (Critical) | ⏱️ ~100+ minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/main/kotlin/com/weeth/domain/board/application/usecase/command/ManagePostUseCase.kt (1)
87-102:⚠️ Potential issue | 🟡 Minor사용되지 않는
user변수
delete메서드에서user를 조회하지만 실제로 사용되지 않습니다. 불필요한 DB 조회를 제거하세요.🔧 수정 제안
`@Transactional` fun delete( clubId: Long, postId: Long, userId: Long, ) { val member = clubMemberPolicy.getActiveMember(clubId, userId) - val user = userReader.getById(userId) val post = findPost(postId) if (post.board.club.id != clubId) throw PostNotFoundException() validateOwner(post, userId) validateWritePermission(post.board, member) markPostFilesDeleted(post.id) post.markDeleted() }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/kotlin/com/weeth/domain/board/application/usecase/command/ManagePostUseCase.kt` around lines 87 - 102, The delete method in ManagePostUseCase is performing an unnecessary DB lookup via userReader.getById(userId) and storing it in the unused variable user; remove the call and the user variable from the delete function (and any now-unused imports) so delete only gets member, post, validates, marks files deleted, and marks the post deleted; update ManagePostUseCase.delete and remove references to userReader.getById to eliminate the redundant query.src/main/kotlin/com/weeth/domain/user/application/usecase/query/GetUserQueryService.kt (1)
3-16: 🛠️ Refactor suggestion | 🟠 MajorUser query에서 Club 도메인 정책을 직접 물지 않는 편이 좋겠습니다.
지금 구조는
GetUserQueryService가ClubMemberPolicy를 통해 club 쪽 조회/판정을 함께 끌어오고 있어서, user usecase 레이어가 다른 도메인 서비스에 직접 묶입니다. 여기서는Reader/Port경계로 active member를 조회하도록 맞춰 두는 편이 의존 방향과 테스트 경계가 덜 흔들립니다. As per coding guidelines, "Cross-domain reads must use Reader interfaces; cross-domain writes call Repository directly."Also applies to: 20-37
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/kotlin/com/weeth/domain/user/application/usecase/query/GetUserQueryService.kt` around lines 3 - 16, GetUserQueryService currently depends directly on ClubMemberPolicy; replace that cross-domain dependency with a read-only port (e.g., define a ClubReader or ClubMemberReader interface in the club domain) and inject that reader into GetUserQueryService instead of ClubMemberPolicy, then change calls that query active membership to use the new reader's method (the methods you currently call on ClubMemberPolicy) so the user use-case stays decoupled from club domain implementation and tests can mock the reader port.
🧹 Nitpick comments (10)
src/test/kotlin/com/weeth/domain/attendance/fixture/AttendanceTestFixture.kt (1)
21-21: 메서드 이름이 실제 동작과 불일치합니다.
createAdminUser는 이제createActiveUser와 동일한 동작을 하므로, 메서드 이름이 오해를 불러일으킵니다. Role이 User 도메인에서 제거되고 ClubMember의 MemberRole로 관리되는 현재 구조에서는 이 메서드를createActiveUserForAdmin으로 이름을 변경하거나, 테스트에서 직접createActiveUser를 호출하도록 리팩토링하는 것이 좋습니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/test/kotlin/com/weeth/domain/attendance/fixture/AttendanceTestFixture.kt` at line 21, 메서드 이름 불일치: 현재 createAdminUser(name: String) 가 createActiveUser(name) 를 그대로 호출해 혼동을 초래하므로, 테스트 픽스처의 createAdminUser를 삭제하거나 이름을 명확히 변경하세요 — 예를 들어 createActiveUserForAdmin으로 리네임하거나 호출부를 직접 createActiveUser로 변경하고, 관련 테스트에서 ClubMember/MemberRole로 역할이 관리되는 현재 도메인 구조를 반영하도록 모든 호출부(createAdminUser 및 해당 테스트 코드)를 업데이트하여 혼동을 없애세요.src/main/kotlin/com/weeth/domain/club/domain/entity/ClubMember.kt (1)
124-128: 메서드 이름이 현재 동작과 맞지 않습니다.Line 124의
updateProfileImageUrl은 URL이 아니라 storageKey를 처리하므로, 호출자 입장에서 의미 오해가 생길 수 있습니다. 메서드명을updateProfileImageStorageKey로 맞추는 것을 권장합니다.✏️ 제안 diff
- fun updateProfileImageUrl(storageKey: String?) { + fun updateProfileImageStorageKey(storageKey: String?) { val trimmed = storageKey?.trim()?.takeIf { it.isNotBlank() } require((trimmed?.length ?: 0) <= 500) { "프로필 이미지 storageKey는 500자 이하여야 합니다." } this.profileImageStorageKey = trimmed }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/kotlin/com/weeth/domain/club/domain/entity/ClubMember.kt` around lines 124 - 128, The method name updateProfileImageUrl on class ClubMember is misleading because it handles a storageKey; rename the function to updateProfileImageStorageKey and update all call sites to use the new name, ensuring the implementation still trims, validates length (<=500) and assigns to profileImageStorageKey; optionally add a small deprecated wrapper named updateProfileImageUrl that delegates to updateProfileImageStorageKey to preserve backward compatibility while you migrate usages.src/main/kotlin/com/weeth/domain/club/domain/service/ClubMemberPolicy.kt (1)
42-47:requireAdmin메서드명과 실제 권한 조건이 불일치합니다.현재는 ADMIN/LEAD 둘 다 허용하므로, 메서드명을
requireAdminOrLead처럼 실제 정책과 맞추는 편이 유지보수에 안전합니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/kotlin/com/weeth/domain/club/domain/service/ClubMemberPolicy.kt` around lines 42 - 47, The method requireAdmin()'s name doesn't match its behavior (it allows ADMIN or LEAD via isAdminOrLead()); rename the function to requireAdminOrLead to reflect the actual policy, keeping the implementation (calls to getActiveMember(...) and the NotClubAdminException) unchanged, and update all call sites to the new requireAdminOrLead identifier so usages compile and intent is clear.src/main/kotlin/com/weeth/domain/dashboard/application/usecase/query/GetDashboardQueryService.kt (1)
82-89:getValue대신 안전한 조회로 예외 전파를 줄여주세요.Line [88]의
memberMap.getValue(post.user.id)는 키가 없을 때 예외를 던집니다. 데이터 불일치가 생기면 게시글 목록 전체가 실패할 수 있어 null-safe 처리(또는 명시적 도메인 예외)로 바꾸는 편이 안전합니다.🔧 제안 수정안
- val memberMap = clubMemberReader.findAllByClubIdAndUserIds(clubId, authorIds).associateBy { it.user.id } + val memberMap = + if (authorIds.isEmpty()) emptyMap() + else clubMemberReader.findAllByClubIdAndUserIds(clubId, authorIds).associateBy { it.user.id } return posts.map { post -> dashboardMapper.toPostResponse( post = post, - authorMember = memberMap.getValue(post.user.id), + authorMember = + memberMap[post.user.id] + ?: throw IllegalStateException("ClubMember not found for postId=${post.id}, userId=${post.user.id}, clubId=$clubId"), files = filesByPostId[post.id] ?: emptyList(), now = now, ) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/kotlin/com/weeth/domain/dashboard/application/usecase/query/GetDashboardQueryService.kt` around lines 82 - 89, Replace the unsafe memberMap.getValue(post.user.id) lookup with a null-safe access and handle missing members explicitly: inside GetDashboardQueryService (where posts are mapped) use memberMap[post.user.id] (or .get(post.user.id)) and then either pass a nullable authorMember to dashboardMapper.toPostResponse or throw a clear domain exception (e.g., MemberNotFoundForPost) with post/user context before calling dashboardMapper.toPostResponse; ensure the change is applied to the posts.map block that currently builds post responses and that clubMemberReader.findAllByClubIdAndUserIds remains the source used to build memberMap.src/main/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubMemberUsecase.kt (2)
46-47:fileAccessUrlPort의존성이 사용되지 않음
fileAccessUrlPort가 주입되었지만, 이 파일 내에서 더 이상 사용되지 않습니다. Line 112에서storageKey를 직접 사용하도록 변경되어 이 의존성이 불필요해 보입니다.♻️ 사용되지 않는 의존성 제거 제안
class ManageClubMemberUsecase( private val clubRepository: ClubRepository, private val clubMemberRepository: ClubMemberRepository, private val clubMemberCardinalRepository: ClubMemberCardinalRepository, private val cardinalReader: CardinalReader, private val sessionReader: SessionReader, private val attendanceRepository: AttendanceRepository, private val userReader: UserReader, private val clubMemberPolicy: ClubMemberPolicy, private val fileRepository: FileRepository, - private val fileAccessUrlPort: FileAccessUrlPort, ) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubMemberUsecase.kt` around lines 46 - 47, The injected dependency fileAccessUrlPort in ManageClubMemberUsecase is unused (you now use storageKey directly); remove the unused constructor/property and any related imports to clean up the class: delete the fileAccessUrlPort parameter from the constructor and any class field named fileAccessUrlPort, update usages if any (ensure storageKey and FileRepository usage remain unchanged), and run a build to remove leftover references or unused import warnings.
112-112: 메서드명과 실제 동작 간 불일치
updateProfileImageUrl메서드가 호출되지만, 이제 URL이 아닌storageKey를 저장합니다. CDN 준비를 위한 변경이라면 메서드명도updateProfileImageStorageKey로 변경하는 것이 일관성 있습니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubMemberUsecase.kt` at line 112, The call members.forEach { it.updateProfileImageUrl(file.storageKey.value) } stores a storageKey, not a URL, so rename the method to reflect the actual data (e.g., updateProfileImageStorageKey) and update all usages; specifically, in ManageClubMemberUsecase.kt change the call to use the new name and refactor the corresponding member/entity method (updateProfileImageUrl -> updateProfileImageStorageKey) so its parameter and implementation store storageKey.value, and update any callers/tests referencing updateProfileImageUrl to the new method name.src/main/kotlin/com/weeth/domain/club/application/mapper/ClubMapper.kt (1)
73-91: LGTM!
storageKey를 통한 이미지 URL 해석 패턴이 일관되게 적용되었습니다.resolveClubImage헬퍼 메서드를 통해 중복 코드를 줄였습니다.참고:
toMemberProfileResponse의 Line 86에서도resolveClubImage헬퍼를 재사용할 수 있지만, 현재 구현도 정상 동작합니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/kotlin/com/weeth/domain/club/application/mapper/ClubMapper.kt` around lines 73 - 91, Refactor toMemberProfileResponse to reuse the resolveClubImage helper instead of resolving profileImageStorageKey inline: replace the expression that builds profileImageUrl from member.profileImageStorageKey?.let { fileAccessUrlPort.resolve(it) } with a call to resolveClubImage(member.profileImageStorageKey) so the logic centralizes in resolveClubImage; update references to symbols to ensure toMemberProfileResponse, resolveClubImage, and member.profileImageStorageKey are used consistently.src/test/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubMemberUseCaseTest.kt (1)
169-170: 메서드 이름을 필드명에 맞춰 변경하기
profileImageStorageKey필드의 이름과 일관성을 위해 메서드 이름을updateProfileImageUrl()에서updateProfileImageStorageKey()로 변경하세요. 현재 메서드명은 필드명, 파라미터명(storageKey), 그리고 메서드 내부 에러 메시지와 모두 불일치합니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/test/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubMemberUseCaseTest.kt` around lines 169 - 170, Rename the method updateProfileImageUrl to updateProfileImageStorageKey across the codebase to match the field profileImageStorageKey: update the method declaration (e.g., in the entity/class that defines updateProfileImageUrl), rename its parameter to storageKey if not already, update internal error/messages to mention "profileImageStorageKey" and "storageKey", and change all call sites such as ManageClubMemberUseCaseTest where member1.updateProfileImageUrl(...) and member2.updateProfileImageUrl(...) are invoked to use member1.updateProfileImageStorageKey(...) / member2.updateProfileImageStorageKey(...).src/test/kotlin/com/weeth/domain/board/application/usecase/query/GetPostQueryServiceTest.kt (1)
140-149: 작성자ClubMember누락 케이스를 한 번 더 묶어두면 좋겠습니다.이번 변경은 author 정보를
clubMemberReader+memberMap경로에서 꺼내오도록 바뀌어서, 탈퇴/비활성 작성자처럼 매핑이 비는 경우를 한 케이스라도 잡아두면 이후 회귀를 훨씬 빨리 확인할 수 있습니다.Also applies to: 285-292
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/test/kotlin/com/weeth/domain/board/application/usecase/query/GetPostQueryServiceTest.kt` around lines 140 - 149, Add a test setup that simulates the author ClubMember being absent from the clubMemberReader mapping so the code path that looks up author via clubMemberReader + memberMap is exercised for a missing/withdrawn author; specifically, in the test around queryService.findPost(...) add an additional stubbed case where clubMemberReader.findAllByClubIdAndUserIds(actualClubId, any()) returns an empty list (or omits the post.authorId) and then assert the behaviour (e.g., postMapper.toDetailResponse is still called with a null/absent member or the expected fallback), adjusting the expected detail/mapper inputs accordingly so the missing-author mapping is covered.src/main/kotlin/com/weeth/domain/user/domain/entity/User.kt (1)
115-122:agreeTerms구현이 약간 중복적이나 의도된 것으로 보임
require(termsAgreed && privacyAgreed)검증 후this.termsAgreed = true로 설정하는 패턴은 파라미터를 그대로 할당해도 동일한 결과입니다. PR 설명에 따르면 경고 제거 목적으로 보이므로 현재 구현도 괜찮습니다.♻️ 선택적 간소화
fun agreeTerms( termsAgreed: Boolean, privacyAgreed: Boolean, ) { require(termsAgreed && privacyAgreed) { "모든 약관에 동의해야 합니다." } - this.termsAgreed = true - this.privacyAgreed = true + this.termsAgreed = termsAgreed + this.privacyAgreed = privacyAgreed }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/kotlin/com/weeth/domain/user/domain/entity/User.kt` around lines 115 - 122, agreeTerms에서 require 검사 후 항상 true로 설정하는 중복을 제거하려면, 검증한 입력값을 그대로 할당하도록 변경하세요: User.agreeTerms 함수에서 require(termsAgreed && privacyAgreed) 검증은 유지하고 this.termsAgreed = true / this.privacyAgreed = true 대신 this.termsAgreed = termsAgreed 및 this.privacyAgreed = privacyAgreed로 할당하여 의도를 명확히 하고 중복 경고를 제거하세요.
🤖 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/kotlin/com/weeth/domain/board/application/usecase/query/GetPostQueryService.kt`:
- Around line 113-118: The code uses memberMap.getValue(post.user.id) which can
throw an uncontrolled NoSuchElementException; replace this with an explicit
lookup and consistent error handling (e.g., val member = memberMap[post.user.id]
?: throw MemberNotFoundException("member not found: ${post.user.id}") ) and pass
that member to postMapper.toListResponse; apply the same change in the
searchPosts path so both places (buildMemberMap usage in GetPostQueryService and
the searchPosts method) handle missing members consistently.
- Around line 85-90: The code uses memberMap.getValue(post.user.id) which will
throw NoSuchElementException for posts authored by withdrawn users; change it to
safely retrieve the member (e.g., memberMap[post.user.id] ?:
createWithdrawnMemberRepresentation()) and pass that into
postMapper.toListResponse (or adjust toListResponse to accept a nullable
member), mirroring the fix used in findPost; reference buildMemberMap,
postMapper.toListResponse, fileExistsByPostId and the posts.map block to apply
the safe-lookup and fallback for withdrawn members.
- Around line 60-66: The current code calls memberMap.getValue(post.user.id)
which throws NoSuchElementException if the post author has no ClubMember entry
(e.g., withdrew); change GetPostQueryService to use memberMap[post.user.id]
(nullable) and handle the null by either providing a safe fallback ClubMember
placeholder (constructed via buildMemberMap or a new factory) or by modifying
postMapper.toDetailResponse to accept a nullable ClubMember? and handle display
for withdrawn users; update the call site in GetPostQueryService so it passes
the safe value (memberMap[post.user.id] ?: fallbackMember) or nullable member
accordingly instead of getValue(...).
In
`@src/main/kotlin/com/weeth/domain/club/application/dto/request/ClubUpdateRequest.kt`:
- Around line 20-27: Add length validation to the ClubUpdateRequest image
storage key fields: annotate profileImageStorageKey and
backgroundImageStorageKey with a validation constraint such as `@field`:Size(max =
500) (from javax.validation.constraints or jakarta.validation.constraints
depending on project) so the DTO enforces a 500-character max at the API
boundary and prevents overly long values reaching the service layer.
In `@src/main/kotlin/com/weeth/domain/club/domain/entity/Club.kt`:
- Around line 107-114: updateImageStorageKey와 엔티티 생성(create 경로)에서
profileImageStorageKey와 backgroundImageStorageKey를 그대로 대입하는 대신 ClubMember와 동일한
규칙(앞뒤 공백 trim, 빈 문자열은 null로 변환, 길이 최대 500자 검증)을 엔티티 내부에서 강제하세요; 구체적으로
updateImageStorageKey 함수와 생성자/팩토리(혹은 create 메서드)에서 입력값을 normalize(trim → if
blank then null)하고 null이 아닐 경우 length <= 500을 검증해 실패시 예외를 throw하거나 적절히 처리하도록
변경하여 엔티티 불변식을 보장합니다 (참조 심볼: updateImageStorageKey, profileImageStorageKey,
backgroundImageStorageKey, ClubMember의 동일 로직).
In
`@src/main/kotlin/com/weeth/domain/club/domain/repository/ClubMemberRepository.kt`:
- Around line 97-109: In GetDashboardQueryService, protect the call to
ClubMemberRepository.findAllByClubIdAndUserIds(clubId, authorIds) by checking if
authorIds is empty and returning an empty map immediately (same pattern used in
GetPostQueryService); this prevents passing an empty collection into the JPQL
"IN :userIds" clause which can fail on some DB dialects. Locate the method in
GetDashboardQueryService where authorIds is built (around the current call site)
and add a guard like "if (authorIds.isEmpty()) return emptyMap()" before
invoking findAllByClubIdAndUserIds; keep the
ClubMemberRepository.findAllByClubIdAndUserIds signature unchanged.
In
`@src/main/kotlin/com/weeth/domain/comment/application/usecase/query/GetCommentQueryService.kt`:
- Around line 64-65: The current use of memberMap.getValue(comment.user.id) in
GetCommentQueryService will throw NoSuchElementException if the author
ClubMember is missing; change this to safely handle absence by using a safe
lookup (e.g., memberMap[comment.user.id]) and then either (1) supply a
fallback/placeholder ClubMember (soft-deleted or unknown author) before calling
commentMapper.toCommentDto, or (2) pass a nullable author into toCommentDto and
let the mapper translate that into a domain-safe representation; ensure you do
not let getValue propagate an exception—convert missing author into a controlled
domain exception or fallback path so comment tree rendering never fails.
---
Outside diff comments:
In
`@src/main/kotlin/com/weeth/domain/board/application/usecase/command/ManagePostUseCase.kt`:
- Around line 87-102: The delete method in ManagePostUseCase is performing an
unnecessary DB lookup via userReader.getById(userId) and storing it in the
unused variable user; remove the call and the user variable from the delete
function (and any now-unused imports) so delete only gets member, post,
validates, marks files deleted, and marks the post deleted; update
ManagePostUseCase.delete and remove references to userReader.getById to
eliminate the redundant query.
In
`@src/main/kotlin/com/weeth/domain/user/application/usecase/query/GetUserQueryService.kt`:
- Around line 3-16: GetUserQueryService currently depends directly on
ClubMemberPolicy; replace that cross-domain dependency with a read-only port
(e.g., define a ClubReader or ClubMemberReader interface in the club domain) and
inject that reader into GetUserQueryService instead of ClubMemberPolicy, then
change calls that query active membership to use the new reader's method (the
methods you currently call on ClubMemberPolicy) so the user use-case stays
decoupled from club domain implementation and tests can mock the reader port.
---
Nitpick comments:
In `@src/main/kotlin/com/weeth/domain/club/application/mapper/ClubMapper.kt`:
- Around line 73-91: Refactor toMemberProfileResponse to reuse the
resolveClubImage helper instead of resolving profileImageStorageKey inline:
replace the expression that builds profileImageUrl from
member.profileImageStorageKey?.let { fileAccessUrlPort.resolve(it) } with a call
to resolveClubImage(member.profileImageStorageKey) so the logic centralizes in
resolveClubImage; update references to symbols to ensure
toMemberProfileResponse, resolveClubImage, and member.profileImageStorageKey are
used consistently.
In
`@src/main/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubMemberUsecase.kt`:
- Around line 46-47: The injected dependency fileAccessUrlPort in
ManageClubMemberUsecase is unused (you now use storageKey directly); remove the
unused constructor/property and any related imports to clean up the class:
delete the fileAccessUrlPort parameter from the constructor and any class field
named fileAccessUrlPort, update usages if any (ensure storageKey and
FileRepository usage remain unchanged), and run a build to remove leftover
references or unused import warnings.
- Line 112: The call members.forEach {
it.updateProfileImageUrl(file.storageKey.value) } stores a storageKey, not a
URL, so rename the method to reflect the actual data (e.g.,
updateProfileImageStorageKey) and update all usages; specifically, in
ManageClubMemberUsecase.kt change the call to use the new name and refactor the
corresponding member/entity method (updateProfileImageUrl ->
updateProfileImageStorageKey) so its parameter and implementation store
storageKey.value, and update any callers/tests referencing updateProfileImageUrl
to the new method name.
In `@src/main/kotlin/com/weeth/domain/club/domain/entity/ClubMember.kt`:
- Around line 124-128: The method name updateProfileImageUrl on class ClubMember
is misleading because it handles a storageKey; rename the function to
updateProfileImageStorageKey and update all call sites to use the new name,
ensuring the implementation still trims, validates length (<=500) and assigns to
profileImageStorageKey; optionally add a small deprecated wrapper named
updateProfileImageUrl that delegates to updateProfileImageStorageKey to preserve
backward compatibility while you migrate usages.
In `@src/main/kotlin/com/weeth/domain/club/domain/service/ClubMemberPolicy.kt`:
- Around line 42-47: The method requireAdmin()'s name doesn't match its behavior
(it allows ADMIN or LEAD via isAdminOrLead()); rename the function to
requireAdminOrLead to reflect the actual policy, keeping the implementation
(calls to getActiveMember(...) and the NotClubAdminException) unchanged, and
update all call sites to the new requireAdminOrLead identifier so usages compile
and intent is clear.
In
`@src/main/kotlin/com/weeth/domain/dashboard/application/usecase/query/GetDashboardQueryService.kt`:
- Around line 82-89: Replace the unsafe memberMap.getValue(post.user.id) lookup
with a null-safe access and handle missing members explicitly: inside
GetDashboardQueryService (where posts are mapped) use memberMap[post.user.id]
(or .get(post.user.id)) and then either pass a nullable authorMember to
dashboardMapper.toPostResponse or throw a clear domain exception (e.g.,
MemberNotFoundForPost) with post/user context before calling
dashboardMapper.toPostResponse; ensure the change is applied to the posts.map
block that currently builds post responses and that
clubMemberReader.findAllByClubIdAndUserIds remains the source used to build
memberMap.
In `@src/main/kotlin/com/weeth/domain/user/domain/entity/User.kt`:
- Around line 115-122: agreeTerms에서 require 검사 후 항상 true로 설정하는 중복을 제거하려면, 검증한
입력값을 그대로 할당하도록 변경하세요: User.agreeTerms 함수에서 require(termsAgreed && privacyAgreed)
검증은 유지하고 this.termsAgreed = true / this.privacyAgreed = true 대신 this.termsAgreed
= termsAgreed 및 this.privacyAgreed = privacyAgreed로 할당하여 의도를 명확히 하고 중복 경고를
제거하세요.
In
`@src/test/kotlin/com/weeth/domain/attendance/fixture/AttendanceTestFixture.kt`:
- Line 21: 메서드 이름 불일치: 현재 createAdminUser(name: String) 가 createActiveUser(name)
를 그대로 호출해 혼동을 초래하므로, 테스트 픽스처의 createAdminUser를 삭제하거나 이름을 명확히 변경하세요 — 예를 들어
createActiveUserForAdmin으로 리네임하거나 호출부를 직접 createActiveUser로 변경하고, 관련 테스트에서
ClubMember/MemberRole로 역할이 관리되는 현재 도메인 구조를 반영하도록 모든 호출부(createAdminUser 및 해당 테스트
코드)를 업데이트하여 혼동을 없애세요.
In
`@src/test/kotlin/com/weeth/domain/board/application/usecase/query/GetPostQueryServiceTest.kt`:
- Around line 140-149: Add a test setup that simulates the author ClubMember
being absent from the clubMemberReader mapping so the code path that looks up
author via clubMemberReader + memberMap is exercised for a missing/withdrawn
author; specifically, in the test around queryService.findPost(...) add an
additional stubbed case where
clubMemberReader.findAllByClubIdAndUserIds(actualClubId, any()) returns an empty
list (or omits the post.authorId) and then assert the behaviour (e.g.,
postMapper.toDetailResponse is still called with a null/absent member or the
expected fallback), adjusting the expected detail/mapper inputs accordingly so
the missing-author mapping is covered.
In
`@src/test/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubMemberUseCaseTest.kt`:
- Around line 169-170: Rename the method updateProfileImageUrl to
updateProfileImageStorageKey across the codebase to match the field
profileImageStorageKey: update the method declaration (e.g., in the entity/class
that defines updateProfileImageUrl), rename its parameter to storageKey if not
already, update internal error/messages to mention "profileImageStorageKey" and
"storageKey", and change all call sites such as ManageClubMemberUseCaseTest
where member1.updateProfileImageUrl(...) and member2.updateProfileImageUrl(...)
are invoked to use member1.updateProfileImageStorageKey(...) /
member2.updateProfileImageStorageKey(...).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 18aa210d-85e7-4fb0-8418-ce78bfeff1d8
📒 Files selected for processing (76)
src/main/kotlin/com/weeth/domain/board/application/dto/request/CreateBoardRequest.ktsrc/main/kotlin/com/weeth/domain/board/application/dto/request/UpdateBoardRequest.ktsrc/main/kotlin/com/weeth/domain/board/application/dto/response/BoardDetailResponse.ktsrc/main/kotlin/com/weeth/domain/board/application/mapper/PostMapper.ktsrc/main/kotlin/com/weeth/domain/board/application/usecase/command/ManagePostUseCase.ktsrc/main/kotlin/com/weeth/domain/board/application/usecase/query/GetBoardQueryService.ktsrc/main/kotlin/com/weeth/domain/board/application/usecase/query/GetPostQueryService.ktsrc/main/kotlin/com/weeth/domain/board/domain/entity/Board.ktsrc/main/kotlin/com/weeth/domain/board/domain/vo/BoardConfig.ktsrc/main/kotlin/com/weeth/domain/board/presentation/BoardAdminController.ktsrc/main/kotlin/com/weeth/domain/board/presentation/BoardController.ktsrc/main/kotlin/com/weeth/domain/board/presentation/PostController.ktsrc/main/kotlin/com/weeth/domain/club/application/dto/request/ClubCreateRequest.ktsrc/main/kotlin/com/weeth/domain/club/application/dto/request/ClubUpdateRequest.ktsrc/main/kotlin/com/weeth/domain/club/application/dto/request/UpdateMemberBioRequest.ktsrc/main/kotlin/com/weeth/domain/club/application/mapper/ClubMapper.ktsrc/main/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubMemberUsecase.ktsrc/main/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubUseCase.ktsrc/main/kotlin/com/weeth/domain/club/domain/entity/Club.ktsrc/main/kotlin/com/weeth/domain/club/domain/entity/ClubMember.ktsrc/main/kotlin/com/weeth/domain/club/domain/enums/MemberRole.ktsrc/main/kotlin/com/weeth/domain/club/domain/repository/ClubMemberReader.ktsrc/main/kotlin/com/weeth/domain/club/domain/repository/ClubMemberRepository.ktsrc/main/kotlin/com/weeth/domain/club/domain/service/ClubMemberPolicy.ktsrc/main/kotlin/com/weeth/domain/comment/application/mapper/CommentMapper.ktsrc/main/kotlin/com/weeth/domain/comment/application/usecase/query/GetCommentQueryService.ktsrc/main/kotlin/com/weeth/domain/dashboard/application/dto/response/DashboardMyInfoResponse.ktsrc/main/kotlin/com/weeth/domain/dashboard/application/mapper/DashboardMapper.ktsrc/main/kotlin/com/weeth/domain/dashboard/application/usecase/query/GetDashboardQueryService.ktsrc/main/kotlin/com/weeth/domain/session/application/usecase/query/GetSessionQueryService.ktsrc/main/kotlin/com/weeth/domain/user/application/dto/request/UserRoleUpdateRequest.ktsrc/main/kotlin/com/weeth/domain/user/application/dto/response/UserInfo.ktsrc/main/kotlin/com/weeth/domain/user/application/dto/response/UserProfileResponse.ktsrc/main/kotlin/com/weeth/domain/user/application/dto/response/UserSummaryResponse.ktsrc/main/kotlin/com/weeth/domain/user/application/mapper/UserMapper.ktsrc/main/kotlin/com/weeth/domain/user/application/usecase/command/SocialLoginUseCase.ktsrc/main/kotlin/com/weeth/domain/user/application/usecase/query/GetUserQueryService.ktsrc/main/kotlin/com/weeth/domain/user/domain/entity/User.ktsrc/main/kotlin/com/weeth/domain/user/domain/enums/Role.ktsrc/main/kotlin/com/weeth/domain/user/presentation/UserController.ktsrc/main/kotlin/com/weeth/global/auth/annotation/CurrentUserRole.ktsrc/main/kotlin/com/weeth/global/auth/jwt/application/service/JwtTokenExtractor.ktsrc/main/kotlin/com/weeth/global/auth/jwt/application/usecase/JwtManageUseCase.ktsrc/main/kotlin/com/weeth/global/auth/jwt/domain/port/RefreshTokenStorePort.ktsrc/main/kotlin/com/weeth/global/auth/jwt/domain/service/JwtTokenProvider.ktsrc/main/kotlin/com/weeth/global/auth/jwt/filter/JwtAuthenticationProcessingFilter.ktsrc/main/kotlin/com/weeth/global/auth/jwt/infrastructure/RedisRefreshTokenStoreAdapter.ktsrc/main/kotlin/com/weeth/global/auth/model/AuthenticatedUser.ktsrc/main/kotlin/com/weeth/global/auth/resolver/CurrentUserRoleArgumentResolver.ktsrc/main/kotlin/com/weeth/global/config/SecurityConfig.ktsrc/main/kotlin/com/weeth/global/config/WebMvcConfig.ktsrc/test/kotlin/com/weeth/domain/attendance/fixture/AttendanceTestFixture.ktsrc/test/kotlin/com/weeth/domain/board/application/mapper/PostMapperTest.ktsrc/test/kotlin/com/weeth/domain/board/application/usecase/command/ManageBoardUseCaseTest.ktsrc/test/kotlin/com/weeth/domain/board/application/usecase/command/ManagePostUseCaseTest.ktsrc/test/kotlin/com/weeth/domain/board/application/usecase/query/GetBoardQueryServiceTest.ktsrc/test/kotlin/com/weeth/domain/board/application/usecase/query/GetPostQueryServiceTest.ktsrc/test/kotlin/com/weeth/domain/board/domain/converter/BoardConfigConverterTest.ktsrc/test/kotlin/com/weeth/domain/board/domain/entity/BoardEntityTest.ktsrc/test/kotlin/com/weeth/domain/board/fixture/BoardTestFixture.ktsrc/test/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubMemberUseCaseTest.ktsrc/test/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubUseCaseTest.ktsrc/test/kotlin/com/weeth/domain/club/application/usecase/query/GetClubMemberQueryServiceTest.ktsrc/test/kotlin/com/weeth/domain/club/domain/entity/ClubTest.ktsrc/test/kotlin/com/weeth/domain/comment/application/usecase/query/CommentQueryPerformanceTest.ktsrc/test/kotlin/com/weeth/domain/comment/application/usecase/query/GetCommentQueryServiceTest.ktsrc/test/kotlin/com/weeth/domain/dashboard/application/usecase/query/GetDashboardQueryServiceTest.ktsrc/test/kotlin/com/weeth/domain/user/application/usecase/command/SocialLoginUseCaseTest.ktsrc/test/kotlin/com/weeth/domain/user/domain/entity/UserTest.ktsrc/test/kotlin/com/weeth/domain/user/fixture/UserTestFixture.ktsrc/test/kotlin/com/weeth/global/auth/jwt/application/service/JwtTokenExtractorTest.ktsrc/test/kotlin/com/weeth/global/auth/jwt/application/usecase/JwtManageUseCaseTest.ktsrc/test/kotlin/com/weeth/global/auth/jwt/domain/service/JwtTokenProviderTest.ktsrc/test/kotlin/com/weeth/global/auth/jwt/filter/JwtAuthenticationProcessingFilterTest.ktsrc/test/kotlin/com/weeth/global/auth/jwt/infrastructure/store/RedisRefreshTokenStoreAdapterTest.ktsrc/test/kotlin/com/weeth/global/auth/resolver/CurrentUserArgumentResolverTest.kt
💤 Files with no reviewable changes (14)
- src/main/kotlin/com/weeth/domain/club/application/dto/request/UpdateMemberBioRequest.kt
- src/main/kotlin/com/weeth/domain/user/domain/enums/Role.kt
- src/main/kotlin/com/weeth/domain/board/presentation/BoardAdminController.kt
- src/test/kotlin/com/weeth/domain/user/fixture/UserTestFixture.kt
- src/test/kotlin/com/weeth/domain/user/domain/entity/UserTest.kt
- src/main/kotlin/com/weeth/domain/dashboard/application/dto/response/DashboardMyInfoResponse.kt
- src/main/kotlin/com/weeth/global/config/WebMvcConfig.kt
- src/main/kotlin/com/weeth/global/auth/model/AuthenticatedUser.kt
- src/main/kotlin/com/weeth/global/auth/jwt/application/service/JwtTokenExtractor.kt
- src/main/kotlin/com/weeth/global/auth/jwt/domain/port/RefreshTokenStorePort.kt
- src/main/kotlin/com/weeth/global/auth/resolver/CurrentUserRoleArgumentResolver.kt
- src/main/kotlin/com/weeth/global/auth/jwt/infrastructure/RedisRefreshTokenStoreAdapter.kt
- src/main/kotlin/com/weeth/global/auth/jwt/domain/service/JwtTokenProvider.kt
- src/main/kotlin/com/weeth/global/auth/annotation/CurrentUserRole.kt
src/main/kotlin/com/weeth/domain/board/application/usecase/query/GetPostQueryService.kt
Show resolved
Hide resolved
src/main/kotlin/com/weeth/domain/board/application/usecase/query/GetPostQueryService.kt
Show resolved
Hide resolved
src/main/kotlin/com/weeth/domain/board/application/usecase/query/GetPostQueryService.kt
Show resolved
Hide resolved
src/main/kotlin/com/weeth/domain/club/application/dto/request/ClubUpdateRequest.kt
Show resolved
Hide resolved
src/main/kotlin/com/weeth/domain/club/domain/repository/ClubMemberRepository.kt
Show resolved
Hide resolved
src/main/kotlin/com/weeth/domain/comment/application/usecase/query/GetCommentQueryService.kt
Show resolved
Hide resolved
# Conflicts: # src/main/kotlin/com/weeth/domain/club/application/dto/request/ClubUpdateRequest.kt # src/main/kotlin/com/weeth/domain/club/application/mapper/ClubMapper.kt # src/main/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubUseCase.kt # src/main/kotlin/com/weeth/domain/club/domain/entity/Club.kt # src/main/kotlin/com/weeth/domain/user/domain/entity/User.kt # src/test/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubUseCaseTest.kt # src/test/kotlin/com/weeth/domain/club/domain/entity/ClubTest.kt
| club.clubContact.phoneNumber shouldBe "01099998888" | ||
| club.clubContact.phoneNumber shouldBe "010-9999-8888" | ||
| club.clubContact.phoneNumber shouldBe "01099998888" |
# Conflicts: # src/main/kotlin/com/weeth/domain/club/domain/entity/ClubMember.kt
📌 Summary
다중 동아리를 지원함에 따라 기존 Jwt에 Role을 저장해 검증하던 로직을 제거하고, 비즈니스 로직 내부에서 검증하도록 수정했습니다.
이미지 URL을 저장하던 것에서 StorageKey를 저장해 추후 CDN 도입시 문제가 없도록 했습니다.
📝 Changes
What
다중 동아리 지원을 위한 인증 정책 수정
이미지 관련 필드 수정
Why
다중 동아리 지원을 위해서, 추후 CDN 도입을 위해서
How
JWT에서 Role 제거 (마스터 토큰 업데이트 완료)
WTH-209 작업에서 추가된 UserInfo에 MemberRole을 반환하도록 처리 + ClubMember의 ProfileImage를 반환하도록 매핑 처리 (N+1이 발생하지 않도록 전부 조회해서 Map으로 변환한 후 꺼내다 쓰는 방식으로 처리했습니다.)
Club, ClubMember에서 사용되는 이미지의 StorageKey를 저장하도록 처리
📸 Screenshots / Logs
💡 Reviewer 참고사항
✅ Checklist
Summary by CodeRabbit
New Features
Bug Fixes
Refactor