Skip to content

[WTH-208] 사용자 프로필 기능 추가#26

Merged
hyxklee merged 40 commits intodevfrom
feat/WTH-208-사용자-프로필-기능-추가
Mar 23, 2026

Hidden character warning

The head ref may contain hidden characters: "feat/WTH-208-\uc0ac\uc6a9\uc790-\ud504\ub85c\ud544-\uae30\ub2a5-\ucd94\uac00"
Merged

[WTH-208] 사용자 프로필 기능 추가#26
hyxklee merged 40 commits intodevfrom
feat/WTH-208-사용자-프로필-기능-추가

Conversation

@soo0711
Copy link
Copy Markdown
Collaborator

@soo0711 soo0711 commented Mar 17, 2026

📌 Summary

어떤 작업인지 한 줄 요약해 주세요.

사용자 프로필 기능을 추가했습니다.

  • 약관 동의
  • 프로필 이미지, 자기소개 필드
  • UserInfo 공통 필드
  • 관련된 TODO 해결

📝 Changes

변경사항을 what, why, how로 구분해 작성해 주세요.

What

  • 약관 동의 API
  • 프로필 이미지 업로드/수정 API
  • OAuth 소셜 로그인 시 프로필 이미지 저장
  • User 엔티티에 termsAgreed, privacyAgreed, bio, profileImageUrl 필드 추가
  • UserInfo 공통 DTO 추가
  • TODO 해결
    • 대시보드 일정 조회 클럽별 필터링 적용
    • User 엔티티 생성자 리팩토링

Why

  • 사용자 프로필 설정/수정 기능 필요
  • 작성자 정보가 여러 도메인 응답에 중복되어 UserInfo로 통합하여 일관성 확보
  • 대시보드 일정이 클럽 단위로 필터링 되게 수정
  • User 엔티티의 lateinit + protected constructor() 패턴을 primary constructor 패턴으로 정리

How

  • AgreeTermsUseCase, UpdateProfileImageUseCase 추가
  • UserInfo.from(user) 팩토리 메서드로 board/comment/dashboard Mapper에서 공통 사용
    • UserInfo가 3개 도메인(board, comment, dashboard)에서 사용되어 특정 도메인 Mapper에 넣으면 cross-domain 의존이 생김.
    • DTO companion object 팩토리로 처리하여 각 도메인 Mapper에서 독립적으로 호출 가능하도록 함
  • EventReader.findByClubIdAndDateRange() 추가하여 클럽 필터링 구현
  • SocialLoginUseCase에서 OAuth 프로필 이미지 URL 전달

📸 Screenshots / Logs

필요시 스크린샷 or 로그를 첨부해주세요.

💡 Reviewer 참고사항

리뷰에 참고할 내용을 작성해주세요.

  • S3 파일 삭제는 soft delete 방식이라 기존 패턴 따랐습니다.

✅ Checklist

  • PR 제목 설정 완료 (WTH-123 인증 필터 설정)
  • 테스트 구현 완료
  • 리뷰어 등록 완료
  • 자체 코드 리뷰 완료

Summary by CodeRabbit

  • 새로운 기능

    • 프로필 이미지 업로드(개인/동아리) 및 업데이트 기능 추가
    • 약관·개인정보 동의 제출 기능 추가
    • 클럽 멤버 자기소개(바이오) 편집 기능 추가
  • 개선 사항

    • 게시물·댓글·대시보드 등에서 작성자 정보가 통합된(author) 형태로 표시 개선
    • 대시보드 일정 조회가 동아리 단위로 제한되어 정확성 향상

@soo0711 soo0711 requested a review from hyxklee March 17, 2026 08:21
@soo0711 soo0711 self-assigned this Mar 17, 2026
@soo0711 soo0711 added ✨ Feature 새로운 기능 추가 🔨 Refactor 코드 구조 개선 및 리팩토링 labels Mar 17, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 17, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

작성자 정보를 UserInfo DTO로 통합하고 프로필 이미지·약관 동의·클럽 멤버 자기소개 기능을 추가했습니다. 대시보드 일정 조회를 클럽 범위로 변경하고 관련 DTO·매퍼·리포지토리·유스케이스·컨트롤러와 테스트를 업데이트했습니다.

Changes

Cohort / File(s) Summary
User DTO / 엔티티 변경
src/main/kotlin/com/weeth/domain/user/application/dto/response/UserInfo.kt, src/main/kotlin/com/weeth/domain/user/domain/entity/User.kt, src/main/kotlin/com/weeth/domain/user/application/dto/response/UserProfileResponse.kt
새로운 UserInfo DTO 추가 및 User 엔티티를 생성자 기반으로 리팩토링. 프로필 이미지 URL 및 약관 동의 필드/메서드(agreeTerms, updateProfileImageUrl) 추가.
게시판·댓글 작성자 통합
src/main/kotlin/com/weeth/domain/board/.../PostDetailResponse.kt, src/main/kotlin/com/weeth/domain/board/.../PostListResponse.kt, src/main/kotlin/com/weeth/domain/board/.../PostMapper.kt, src/main/kotlin/com/weeth/domain/comment/.../CommentResponse.kt, src/main/kotlin/com/weeth/domain/comment/.../CommentMapper.kt
name/role 필드를 제거하고 author: UserInfo로 통합. 매퍼에서 UserInfo.from(...)로 작성자 정보 구성.
대시보드 및 일정 조회 변경
src/main/kotlin/com/weeth/domain/dashboard/.../DashboardMyInfoResponse.kt, src/main/kotlin/com/weeth/domain/dashboard/.../DashboardPostResponse.kt, src/main/kotlin/com/weeth/domain/dashboard/.../DashboardMapper.kt, src/main/kotlin/com/weeth/domain/dashboard/.../GetDashboardQueryService.kt
대시보드 응답을 UserInfo 기반으로 전환. toMyInfoResponse 시그니처 변경(클럽 멤버 인자 추가) 및 일정 조회를 클럽 범위 쿼리로 전환.
파일 소유 타입 및 리포지토리 확장
src/main/kotlin/com/weeth/domain/file/domain/enums/FileOwnerType.kt, src/main/kotlin/com/weeth/domain/schedule/domain/repository/EventReader.kt, src/main/kotlin/com/weeth/domain/schedule/domain/repository/EventRepository.kt
FileOwnerTypeUSER_PROFILE, CLUB_MEMBER_PROFILE 추가. 이벤트 조회에 findByClubIdAndDateRange 메서드 추가 및 구현 위임.
약관 동의 기능 추가
src/main/kotlin/com/weeth/domain/user/application/dto/request/AgreeTermsRequest.kt, src/main/kotlin/com/weeth/domain/user/application/usecase/command/AgreeTermsUseCase.kt, src/main/kotlin/com/weeth/domain/user/presentation/UserController.kt, src/main/kotlin/com/weeth/domain/user/presentation/UserResponseCode.kt
약관 동의 요청 DTO 및 유스케이스 추가. UserController에 POST /terms 엔드포인트와 성공 응답 코드 추가.
프로필 이미지 관리 기능
src/main/kotlin/com/weeth/domain/user/application/usecase/command/UpdateProfileImageUseCase.kt, src/main/kotlin/com/weeth/domain/user/presentation/UserController.kt
사용자 프로필 이미지 업데이트 유스케이스 추가: 기존 프로필 파일 삭제 표기, 새 파일 생성/저장, 사용자 프로필 URL 갱신. 컨트롤러에 PUT /profile-image 엔드포인트 추가.
클럽 멤버 프로필(이미지·자기소개) 추가
src/main/kotlin/com/weeth/domain/club/application/dto/request/UpdateMemberBioRequest.kt, src/main/kotlin/com/weeth/domain/club/application/dto/response/ClubMemberProfileResponse.kt, src/main/kotlin/com/weeth/domain/club/domain/entity/ClubMember.kt, src/main/kotlin/com/weeth/domain/club/application/mapper/ClubMapper.kt, src/main/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubMemberUsecase.kt, src/main/kotlin/com/weeth/domain/club/presentation/ClubController.kt, src/main/kotlin/com/weeth/domain/club/presentation/ClubResponseCode.kt
ClubMemberprofileImageUrl·bio 필드 및 업데이트 메서드 추가. 멤버 프로필 이미지·자기소개 갱신 API와 usecase 구현 추가.
소셜 로그인·인증 DTO 변경
src/main/kotlin/com/weeth/domain/user/domain/vo/SocialAuthResult.kt, src/main/kotlin/com/weeth/domain/user/infrastructure/.../AppleSocialAuthAdapter.kt, src/main/kotlin/com/weeth/domain/user/infrastructure/.../KakaoSocialAuthAdapter.kt, src/main/kotlin/com/weeth/global/auth/kakao/dto/KakaoProfile.kt
SocialAuthResult에서 profileImageUrl 제거 및 관련 어댑터에서 해당 필드 삭제. KakaoProfile의 profileImageUrl 필드 제거.
매퍼·DTO 명명 및 필드 매핑 정리
src/main/kotlin/com/weeth/domain/user/application/mapper/UserMapper.kt, src/main/kotlin/com/weeth/domain/user/application/dto/request/UpdateUserProfileRequest.kt
UserProfileResponse·UserSummaryResponse 매핑을 명명된 파라미터로 변경, profileImageUrlcardinals 필드 매핑 추가.
테스트·픽스처 업데이트
src/test/kotlin/.../*, src/test/kotlin/com/weeth/domain/user/fixture/UserTestFixture.kt, src/test/kotlin/com/weeth/domain/club/fixture/ClubMemberTestFixture.kt
테스트 전반에서 UserInfo 사용으로 기대값 변경. 대시보드/세션/이벤트 모킹을 클럽 범위 메서드로 전환. 테스트 픽스처의 ID 할당 방식을 ReflectionTestUtils로 변경. 새 테스트 추가: AgreeTermsUseCaseTest, UpdateProfileImageUseCaseTest 등.

Sequence Diagram(s)

sequenceDiagram
    actor Client
    participant Controller as UserController
    participant UseCase as UpdateProfileImageUseCase
    participant FileRepo as FileRepository
    participant FileAccess as FileAccessUrlPort
    participant UserRepo as UserRepository

    Client->>Controller: PUT /users/me/profile-image (FileSaveRequest)
    Controller->>UseCase: execute(userId, request)
    UseCase->>UserRepo: getById(userId)
    UserRepo-->>UseCase: User
    UseCase->>FileRepo: findAllByOwnerTypeAndOwnerIdAndStatus(CLUB_MEMBER_PROFILE/USER_PROFILE, userId, UPLOADED)
    FileRepo-->>UseCase: [existingFiles]
    UseCase->>FileRepo: mark existingFiles as DELETED (save)
    UseCase->>FileRepo: save(new File owner=USER_PROFILE, metadata)
    FileRepo-->>UseCase: savedFile
    UseCase->>FileAccess: resolve(savedFile.storageKey)
    FileAccess-->>UseCase: profileImageUrl
    UseCase->>UserRepo: update user's profileImageUrl and persist (via user entity mutation)
    UseCase-->>Controller: success
    Controller-->>Client: 200 USER_PROFILE_IMAGE_UPDATE_SUCCESS
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested reviewers

  • hyxklee
  • JIN921
  • nabbang6

Poem

🐰 작성자 한데 모아 UserInfo로 빛나네
프로필엔 사진 달고, 약관엔 체크 퐁당
클럽엔 자기소개 한 줄 달아두고
대시보드는 클럽별로 슥슥 정리해요
당근 한 움큼 축하합니다 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.51% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 주요 변경사항인 '사용자 프로필 기능 추가'를 명확하게 요약하고 있으며, 이는 약관 동의, 프로필 이미지, 자기소개 등 다양한 기능 추가를 포함한 changeset의 주요 목적을 반영합니다.
Description check ✅ Passed PR 설명은 template의 모든 주요 섹션(Summary, Changes의 What/Why/How, Reviewer 참고사항, Checklist)을 포함하고 있으며, 변경사항을 구체적으로 설명하고 있습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/WTH-208-사용자-프로필-기능-추가

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

You can enable review details to help with troubleshooting, context usage and more.

Enable the reviews.review_details setting to include review details such as the model used, the time taken for each step and more in the review comments.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/kotlin/com/weeth/domain/user/application/usecase/command/UpdateUserProfileUseCase.kt (1)

23-30: ⚠️ Potential issue | 🟠 Major

bio 미전달 요청이 기존 자기소개를 지워버립니다.

UpdateUserProfileRequest.bio가 기본값 null인데 여기서 user.update()에 그대로 넘기고 있어서, bio를 보내지 않은 요청도 기존 값을 null로 덮어씁니다. 구버전 클라이언트나 부분 수정 시나리오가 있다면 사용자 데이터가 사라집니다. 전체 치환 API면 bio를 필수로 바꾸고, 부분 수정 API면 미전달과 명시적 null을 구분할 수 있는 요청 모델이 필요합니다.

🤖 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/command/UpdateUserProfileUseCase.kt`
around lines 23 - 30, UpdateUserProfileRequest.bio being nullable is passed
directly into user.update(...) which causes omission of bio in the request to
overwrite the existing bio with null; change the call site in
UpdateUserProfileUseCase (the user.update(...) invocation) to avoid overwriting
when bio was not supplied—either pass the existing value when request.bio is
null (e.g., bio = request.bio ?: user.bio) or change the request model to
distinguish “not provided” (e.g., Optional/Wrapper) and only include bio in the
update when present; update the call to user.update(...) accordingly so
UpdateUserProfileRequest.bio no longer erases the stored bio unintentionally.
🧹 Nitpick comments (2)
src/test/kotlin/com/weeth/domain/user/application/usecase/command/UpdateProfileImageUseCaseTest.kt (1)

61-61: 도메인 엔티티는 모킹 대신 실제 객체 검증이 더 적합합니다

Line [61]의 mockk<File>(relaxed = true) 대신 File.createUploaded(...)로 실제 객체를 만들고, 실행 후 statusDELETED로 바뀌는지 검증해 주세요. 엔티티 동작 테스트의 신뢰성이 더 높아집니다.

As per coding guidelines: Use MockK and springmockk for mocking in Kotlin tests; mock Repository/Reader/Port interfaces, not implementations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/kotlin/com/weeth/domain/user/application/usecase/command/UpdateProfileImageUseCaseTest.kt`
at line 61, In UpdateProfileImageUseCaseTest replace the mocked domain entity
existingFile (mockk<File>(relaxed = true)) with a real instance created via
File.createUploaded(...) and run the use case, then assert the entity's status
field equals DELETED after execution; specifically, instantiate the domain
object with File.createUploaded(...) instead of mocking, invoke the
UpdateProfileImageUseCase, and verify existingFile.status == DELETED (do not
mock domain entities—only mock repository/port interfaces).
src/test/kotlin/com/weeth/domain/board/application/mapper/PostMapperTest.kt (1)

23-26: 엔티티 Mock 스텁 확장을 줄이고 Fixture 기반으로 전환해 주세요.

User 엔티티 프로퍼티 스텁을 늘리는 방식은 테스트를 구현 세부사항에 강결합시킵니다. 이 테스트는 실제 fixture 객체를 만들고 Reader/Port 인터페이스만 MockK로 모킹하는 형태가 더 안정적입니다.

Based on learnings: Applies to **/*Test.kt : Use MockK and springmockk for mocking in Kotlin tests; mock Repository/Reader/Port interfaces, not implementations

🤖 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/mapper/PostMapperTest.kt`
around lines 23 - 26, The test currently over-mocks the User entity via repeated
stubs (every { user.id } returns ..., every { user.name } returns ..., etc.);
instead create a real User fixture object (e.g., a test User instance with
id=1L, name="테스터", role=Role.USER, profileImageUrl=null) and stop stubbing its
properties, and only mock the reader/port interfaces the test depends on (e.g.,
UserReader/UserPort or repository used by PostMapperTest) with MockK/springmockk
to return the fixture when invoked; update PostMapperTest to use that fixture in
place of the mocked `user` variable and adjust any mocked calls to target the
interface methods (e.g., every { userReader.findById(...) } returns fixture)
rather than the entity properties.
🤖 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/user/application/usecase/command/UpdateProfileImageUseCase.kt`:
- Around line 26-31: The UpdateProfileImageUseCase currently calls
FileRepository.findAllByOwnerTypeAndOwnerIdAndStatus directly (using
FileOwnerType.USER_PROFILE, user.id, FileStatus.UPLOADED) — move that
cross-domain read into a new Reader interface (e.g., FileReader with a method
like findAllByOwnerTypeAndOwnerIdAndStatus or findUploadedByOwner) and inject
FileReader into UpdateProfileImageUseCase; replace the direct repository read
with a call to the FileReader, but keep the write mutation (calling markDeleted
on each File entity and persisting with FileRepository.save or the existing
repository delete/save) in the use case so that only reads use the reader and
writes still use FileRepository.

In `@src/main/kotlin/com/weeth/domain/user/domain/entity/User.kt`:
- Around line 71-85: Add a database migration to alter the users table for the
new non-nullable fields on the User entity: create a migration that adds the
columns termsAgreed and privacyAgreed (and any other added columns like
profileImageUrl/bio if not present) and ensure existing rows are backfilled
before enforcing NOT NULL (either by creating the columns with a DEFAULT false
and then dropping the default, or adding as nullable, updating all rows to
false, and then altering to NOT NULL); reference the User class properties
termsAgreed and privacyAgreed when naming/validating the migration and ensure
the migration is included in the deployment pipeline so the schema change is
applied before this code is released.

---

Outside diff comments:
In
`@src/main/kotlin/com/weeth/domain/user/application/usecase/command/UpdateUserProfileUseCase.kt`:
- Around line 23-30: UpdateUserProfileRequest.bio being nullable is passed
directly into user.update(...) which causes omission of bio in the request to
overwrite the existing bio with null; change the call site in
UpdateUserProfileUseCase (the user.update(...) invocation) to avoid overwriting
when bio was not supplied—either pass the existing value when request.bio is
null (e.g., bio = request.bio ?: user.bio) or change the request model to
distinguish “not provided” (e.g., Optional/Wrapper) and only include bio in the
update when present; update the call to user.update(...) accordingly so
UpdateUserProfileRequest.bio no longer erases the stored bio unintentionally.

---

Nitpick comments:
In `@src/test/kotlin/com/weeth/domain/board/application/mapper/PostMapperTest.kt`:
- Around line 23-26: The test currently over-mocks the User entity via repeated
stubs (every { user.id } returns ..., every { user.name } returns ..., etc.);
instead create a real User fixture object (e.g., a test User instance with
id=1L, name="테스터", role=Role.USER, profileImageUrl=null) and stop stubbing its
properties, and only mock the reader/port interfaces the test depends on (e.g.,
UserReader/UserPort or repository used by PostMapperTest) with MockK/springmockk
to return the fixture when invoked; update PostMapperTest to use that fixture in
place of the mocked `user` variable and adjust any mocked calls to target the
interface methods (e.g., every { userReader.findById(...) } returns fixture)
rather than the entity properties.

In
`@src/test/kotlin/com/weeth/domain/user/application/usecase/command/UpdateProfileImageUseCaseTest.kt`:
- Line 61: In UpdateProfileImageUseCaseTest replace the mocked domain entity
existingFile (mockk<File>(relaxed = true)) with a real instance created via
File.createUploaded(...) and run the use case, then assert the entity's status
field equals DELETED after execution; specifically, instantiate the domain
object with File.createUploaded(...) instead of mocking, invoke the
UpdateProfileImageUseCase, and verify existingFile.status == DELETED (do not
mock domain entities—only mock repository/port interfaces).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c5c27ebf-7939-4b8a-98ee-5f939d5cbd79

📥 Commits

Reviewing files that changed from the base of the PR and between 5d3d4f4 and 431d64c.

📒 Files selected for processing (31)
  • src/main/kotlin/com/weeth/domain/board/application/dto/response/PostDetailResponse.kt
  • src/main/kotlin/com/weeth/domain/board/application/dto/response/PostListResponse.kt
  • src/main/kotlin/com/weeth/domain/board/application/mapper/PostMapper.kt
  • src/main/kotlin/com/weeth/domain/comment/application/dto/response/CommentResponse.kt
  • src/main/kotlin/com/weeth/domain/comment/application/mapper/CommentMapper.kt
  • src/main/kotlin/com/weeth/domain/dashboard/application/dto/response/DashboardMyInfoResponse.kt
  • src/main/kotlin/com/weeth/domain/dashboard/application/dto/response/DashboardPostResponse.kt
  • src/main/kotlin/com/weeth/domain/dashboard/application/mapper/DashboardMapper.kt
  • src/main/kotlin/com/weeth/domain/dashboard/application/usecase/query/GetDashboardQueryService.kt
  • src/main/kotlin/com/weeth/domain/file/domain/enums/FileOwnerType.kt
  • src/main/kotlin/com/weeth/domain/schedule/domain/repository/EventReader.kt
  • src/main/kotlin/com/weeth/domain/schedule/domain/repository/EventRepository.kt
  • src/main/kotlin/com/weeth/domain/user/application/dto/request/AgreeTermsRequest.kt
  • src/main/kotlin/com/weeth/domain/user/application/dto/request/UpdateUserProfileRequest.kt
  • src/main/kotlin/com/weeth/domain/user/application/dto/response/UserInfo.kt
  • src/main/kotlin/com/weeth/domain/user/application/dto/response/UserProfileResponse.kt
  • src/main/kotlin/com/weeth/domain/user/application/mapper/UserMapper.kt
  • src/main/kotlin/com/weeth/domain/user/application/usecase/command/AgreeTermsUseCase.kt
  • src/main/kotlin/com/weeth/domain/user/application/usecase/command/SocialLoginUseCase.kt
  • src/main/kotlin/com/weeth/domain/user/application/usecase/command/UpdateProfileImageUseCase.kt
  • src/main/kotlin/com/weeth/domain/user/application/usecase/command/UpdateUserProfileUseCase.kt
  • src/main/kotlin/com/weeth/domain/user/domain/entity/User.kt
  • src/main/kotlin/com/weeth/domain/user/presentation/UserController.kt
  • src/main/kotlin/com/weeth/domain/user/presentation/UserResponseCode.kt
  • src/test/kotlin/com/weeth/domain/board/application/mapper/PostMapperTest.kt
  • src/test/kotlin/com/weeth/domain/board/application/usecase/query/GetPostQueryServiceTest.kt
  • src/test/kotlin/com/weeth/domain/comment/application/usecase/query/GetCommentQueryServiceTest.kt
  • src/test/kotlin/com/weeth/domain/dashboard/application/usecase/query/GetDashboardQueryServiceTest.kt
  • src/test/kotlin/com/weeth/domain/user/application/usecase/command/AgreeTermsUseCaseTest.kt
  • src/test/kotlin/com/weeth/domain/user/application/usecase/command/UpdateProfileImageUseCaseTest.kt
  • src/test/kotlin/com/weeth/domain/user/domain/entity/UserTest.kt

Comment on lines +26 to +31
fileRepository
.findAllByOwnerTypeAndOwnerIdAndStatus(
FileOwnerType.USER_PROFILE,
user.id,
FileStatus.UPLOADED,
).forEach { it.markDeleted() }
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

타 도메인 조회를 Repository로 직접 수행하고 있습니다

Line [26]~Line [31]은 user 도메인 UseCase에서 file 도메인 조회를 FileRepository로 직접 수행합니다. 조회는 Reader 인터페이스로 분리하고, 저장(save)만 Repository 직접 호출로 유지해 주세요.

As per coding guidelines: Cross-domain reads must use Reader interfaces; cross-domain writes call Repository directly.

🤖 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/command/UpdateProfileImageUseCase.kt`
around lines 26 - 31, The UpdateProfileImageUseCase currently calls
FileRepository.findAllByOwnerTypeAndOwnerIdAndStatus directly (using
FileOwnerType.USER_PROFILE, user.id, FileStatus.UPLOADED) — move that
cross-domain read into a new Reader interface (e.g., FileReader with a method
like findAllByOwnerTypeAndOwnerIdAndStatus or findUploadedByOwner) and inject
FileReader into UpdateProfileImageUseCase; replace the direct repository read
with a call to the FileReader, but keep the write mutation (calling markDeleted
on each File entity and persisting with FileRepository.save or the existing
repository delete/save) in the use case so that only reads use the reader and
writes still use FileRepository.

Comment on lines +71 to +85
@Column(nullable = false)
var termsAgreed: Boolean = false
private set

@Column(nullable = false)
var privacyAgreed: Boolean = false
private set

@Column(length = 200)
var bio: String? = null
private set

@Column(length = 500)
var profileImageUrl: String? = profileImageUrl
private set
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## Candidate migration/changelog files"
fd -a 'V[0-9].*\.sql$' || true
fd -a '.*(migration|migrations|changelog|flyway|liquibase|schema).*' || true

echo
echo "## Search for new users columns"
rg -n -C2 \
  -g '*.sql' -g '*.xml' -g '*.yml' -g '*.yaml' -g '*.json' \
  'terms[_]?agreed|privacy[_]?agreed|profile[_]?image[_]?url|\bbio\b' || true

Repository: Team-Weeth/weeth-server

Length of output: 138


users 테이블 마이그레이션 추가 필요

데이터베이스 마이그레이션이 없습니다. termsAgreed, privacyAgreed 컬럼이 nullable = false이므로, 마이그레이션 없이 배포하면 기존 데이터 행에 대해 스키마 적용이 실패합니다. 마이그레이션 파일을 작성하여 새 컬럼을 추가하고 NOT NULL 제약에 대한 기본값 또는 backfill 처리를 포함해야 합니다.

🤖 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 71
- 85, Add a database migration to alter the users table for the new non-nullable
fields on the User entity: create a migration that adds the columns termsAgreed
and privacyAgreed (and any other added columns like profileImageUrl/bio if not
present) and ensure existing rows are backfilled before enforcing NOT NULL
(either by creating the columns with a DEFAULT false and then dropping the
default, or adding as nullable, updating all rows to false, and then altering to
NOT NULL); reference the User class properties termsAgreed and privacyAgreed
when naming/validating the migration and ensure the migration is included in the
deployment pipeline so the schema change is applied before this code is
released.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 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/user/domain/entity/User.kt`:
- Around line 137-138: The updateProfileImageUrl method currently sets
profileImageUrl to url?.trim(), which stores purely-whitespace input as an empty
string; change the assignment in User.updateProfileImageUrl to normalize
whitespace-only values to null by trimming the input and converting any
resulting empty string to null so profileImageUrl is either a non-empty trimmed
string or null (preserve handling of an original null input).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d169a8b7-ea04-4820-b0e7-ac4dde7cf1ab

📥 Commits

Reviewing files that changed from the base of the PR and between 431d64c and be3e3f4.

📒 Files selected for processing (2)
  • src/main/kotlin/com/weeth/domain/user/application/dto/request/UpdateUserProfileRequest.kt
  • src/main/kotlin/com/weeth/domain/user/domain/entity/User.kt

Comment thread src/main/kotlin/com/weeth/domain/user/domain/entity/User.kt Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 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/user/domain/entity/User.kt`:
- Line 84: The constructor currently assigns var profileImageUrl: String? =
profileImageUrl without normalizing blank strings, allowing empty strings into
the DB; apply the same normalization used by updateProfileImageUrl() when
initializing the property (e.g., trim and convert empty/blank strings to null)
so the User entity always stores a normalized nullable profileImageUrl; update
the initialization in the User class to call the same normalization helper or
inline logic to ensure consistency with updateProfileImageUrl().
- Around line 22-40: 생성자에서 공백만 입력된 이름을 허용해 도메인 불변식이 깨지므로 User 클래스 생성자 초기화부(현재
var name: String = name.trim())와 일치하도록 생성 시에도 공백 검증을 추가하세요; 예를 들어 User 생성자에서
name을 먼저 trim()한 뒤 isNotBlank()를 검사하고(조건 불충족 시 IllegalArgumentException 또는
require로 예외 발생) 검증된 trim된 값을 var name에 할당하도록 수정해 update()와 동일한 검증 로직을 적용하세요.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 65e9eb45-b628-42af-8218-7d21d8e6bdeb

📥 Commits

Reviewing files that changed from the base of the PR and between be3e3f4 and fca4dc0.

📒 Files selected for processing (1)
  • src/main/kotlin/com/weeth/domain/user/domain/entity/User.kt

Comment thread src/main/kotlin/com/weeth/domain/user/domain/entity/User.kt Outdated
Comment thread src/main/kotlin/com/weeth/domain/user/domain/entity/User.kt Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
src/main/kotlin/com/weeth/domain/user/application/mapper/UserMapper.kt (1)

22-41: cardinals = emptyList() 하드코딩은 향후 수정이 필요합니다.

toUserProfileResponsetoUserSummaryResponse 모두 cardinals를 빈 리스트로 하드코딩하고 있습니다. 관련 코드(GetUserQueryService.kt:18)의 TODO 주석("동아리별 정보 추가")을 보면 이것이 의도된 임시 구현임을 알 수 있습니다.

명명된 파라미터 사용으로 가독성이 향상된 점은 좋습니다. 다만 cardinals 데이터 연동 작업이 누락되지 않도록 추적이 필요합니다.

cardinals 데이터를 실제로 연동하는 구현을 도와드릴까요, 아니면 이 작업을 추적할 이슈를 생성해 드릴까요?

🤖 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/mapper/UserMapper.kt`
around lines 22 - 41, Replace the hardcoded cardinals = emptyList() in
toUserProfileResponse and toUserSummaryResponse with a real mapping from the
domain user.cardinals collection (or the field added by GetUserQueryService's
"동아리별 정보 추가" work): e.g., use user.cardinals?.map { /* map to the appropriate
DTO like CardinalResponse or CardinalSummary */ } ?: emptyList() so null is
handled; update the mapper function names to reference the actual response DTO
type you map to and ensure the mapping logic is applied in both
toUserProfileResponse and toUserSummaryResponse instead of the temporary
emptyList().
src/test/kotlin/com/weeth/domain/board/application/usecase/command/MarkNoticeReadUseCaseTest.kt (1)

50-50: userId 상수를 그대로 재사용해주세요.

Line 50만 1L로 고정돼 있어서 위의 userId 값이 바뀌면 fixture ID만 조용히 어긋납니다. 여기서도 userId를 넘기는 편이 테스트 의도가 더 명확합니다.

♻️ 제안
-            val user = UserTestFixture.createActiveUser1(1L)
+            val user = UserTestFixture.createActiveUser1(userId)
🤖 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/command/MarkNoticeReadUseCaseTest.kt`
at line 50, Replace the hard-coded fixture ID with the test's userId constant:
in MarkNoticeReadUseCaseTest change the call
UserTestFixture.createActiveUser1(1L) to use the existing userId variable (e.g.,
UserTestFixture.createActiveUser1(userId)) so the fixture ID stays in sync with
the test's userId and the test intent is clearer.
src/test/kotlin/com/weeth/domain/board/application/usecase/command/ManagePostUseCaseTest.kt (1)

382-396: 테스트 헬퍼 함수 사용의 일관성 검토

owner 변수는 UserTestFixture.createActiveUser1(1L)을 사용하고, otherUsercreateUser(2L, Role.USER)를 사용하고 있습니다. 두 방식 모두 동작하지만, 동일 테스트 파일 내에서 일관된 방식을 사용하는 것이 좋습니다.

♻️ 일관성을 위한 제안
 describe("owner validation") {
     it("작성자가 아니면 수정 시 예외를 던진다") {
-        val owner = UserTestFixture.createActiveUser1(1L)
+        val owner = createUser(1L, Role.USER)
         val otherUser = createUser(2L, Role.USER)
🤖 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/command/ManagePostUseCaseTest.kt`
around lines 382 - 396, The test mixes two user-creation helpers
(UserTestFixture.createActiveUser1(1L) for owner and createUser(2L, Role.USER)
for otherUser); make them consistent by using the same fixture API for both
users—either change otherUser to use the UserTestFixture helper (e.g.,
UserTestFixture.createActiveUserX(2L) or equivalent) or change owner to use
createUser(1L, Role.USER)—update the creation of owner or otherUser so both
reference the same helper method (owner, otherUser,
UserTestFixture.createActiveUser1, createUser).
🤖 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/club/application/dto/request/UpdateMemberBioRequest.kt`:
- Around line 6-9: The UpdateMemberBioRequest currently declares val bio:
String? = null so omitted fields bind as null and cannot distinguish "missing"
vs "explicitly clear"; if you don't support explicit clearing, make bio required
by changing the property to a non-null type (e.g., val bio: String) and add a
validation annotation such as `@field`:NotNull or `@field`:NotBlank on
UpdateMemberBioRequest to cause a 400 when the field is omitted; alternatively,
if you do support clearing, add an explicit flag (e.g., val clearBio: Boolean? =
null) alongside bio to signal deletion instead of relying on bio == null.

In `@src/main/kotlin/com/weeth/domain/user/domain/entity/User.kt`:
- Around line 78-80: The profileImageUrl property currently only
trims/null-normalizes but does not enforce the 500-char column length, so long
values may violate DB constraints; update the normalization logic used during
construction/update (the code paths or function that currently sets
profileImageUrl) to also enforce length by truncating or rejecting strings
beyond 500 chars (e.g., apply .takeIf { it.length <= 500 } or .take(500) before
assignment) and ensure the same change is applied to the other occurrences
mentioned (lines referenced around profileImageUrl and the similar block at
128-130) so application-level validation matches the `@Column`(length = 500)
constraint.
- Around line 39-40: The SocialLoginUseCase currently forwards authResult.name
?: "" into User.create(), which now rejects blank names; update the
SocialLoginUseCase logic (the method handling authResult in SocialLoginUseCase
where authResult.name is read around the referenced ranges) to detect
missing/blank authResult.name and either (a) substitute a safe fallback (e.g.,
authResult.nickname, provider displayName, or a generated placeholder like
"user_<provider>_<id>") before calling User.create(), or (b) branch to a
name-completion flow that returns a pending-registration state instead of
calling User.create(); ensure you reference authResult.name and User.create()
when implementing the fix.

---

Nitpick comments:
In `@src/main/kotlin/com/weeth/domain/user/application/mapper/UserMapper.kt`:
- Around line 22-41: Replace the hardcoded cardinals = emptyList() in
toUserProfileResponse and toUserSummaryResponse with a real mapping from the
domain user.cardinals collection (or the field added by GetUserQueryService's
"동아리별 정보 추가" work): e.g., use user.cardinals?.map { /* map to the appropriate
DTO like CardinalResponse or CardinalSummary */ } ?: emptyList() so null is
handled; update the mapper function names to reference the actual response DTO
type you map to and ensure the mapping logic is applied in both
toUserProfileResponse and toUserSummaryResponse instead of the temporary
emptyList().

In
`@src/test/kotlin/com/weeth/domain/board/application/usecase/command/ManagePostUseCaseTest.kt`:
- Around line 382-396: The test mixes two user-creation helpers
(UserTestFixture.createActiveUser1(1L) for owner and createUser(2L, Role.USER)
for otherUser); make them consistent by using the same fixture API for both
users—either change otherUser to use the UserTestFixture helper (e.g.,
UserTestFixture.createActiveUserX(2L) or equivalent) or change owner to use
createUser(1L, Role.USER)—update the creation of owner or otherUser so both
reference the same helper method (owner, otherUser,
UserTestFixture.createActiveUser1, createUser).

In
`@src/test/kotlin/com/weeth/domain/board/application/usecase/command/MarkNoticeReadUseCaseTest.kt`:
- Line 50: Replace the hard-coded fixture ID with the test's userId constant: in
MarkNoticeReadUseCaseTest change the call UserTestFixture.createActiveUser1(1L)
to use the existing userId variable (e.g.,
UserTestFixture.createActiveUser1(userId)) so the fixture ID stays in sync with
the test's userId and the test intent is clearer.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0312b6fb-dbb3-4931-831a-d1591d129bb0

📥 Commits

Reviewing files that changed from the base of the PR and between fca4dc0 and 259c71b.

📒 Files selected for processing (19)
  • src/main/kotlin/com/weeth/domain/club/application/dto/request/UpdateMemberBioRequest.kt
  • src/main/kotlin/com/weeth/domain/club/application/dto/response/ClubMemberProfileResponse.kt
  • src/main/kotlin/com/weeth/domain/club/application/mapper/ClubMapper.kt
  • src/main/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubMemberUsecase.kt
  • src/main/kotlin/com/weeth/domain/club/domain/entity/ClubMember.kt
  • src/main/kotlin/com/weeth/domain/club/presentation/ClubController.kt
  • src/main/kotlin/com/weeth/domain/club/presentation/ClubResponseCode.kt
  • src/main/kotlin/com/weeth/domain/dashboard/application/mapper/DashboardMapper.kt
  • src/main/kotlin/com/weeth/domain/dashboard/application/usecase/query/GetDashboardQueryService.kt
  • src/main/kotlin/com/weeth/domain/user/application/dto/request/UpdateUserProfileRequest.kt
  • src/main/kotlin/com/weeth/domain/user/application/dto/response/UserProfileResponse.kt
  • src/main/kotlin/com/weeth/domain/user/application/mapper/UserMapper.kt
  • src/main/kotlin/com/weeth/domain/user/domain/entity/User.kt
  • src/test/kotlin/com/weeth/domain/board/application/usecase/command/ManagePostUseCaseTest.kt
  • src/test/kotlin/com/weeth/domain/board/application/usecase/command/MarkNoticeReadUseCaseTest.kt
  • src/test/kotlin/com/weeth/domain/club/application/usecase/query/GetClubMemberQueryServiceTest.kt
  • src/test/kotlin/com/weeth/domain/dashboard/application/usecase/query/GetDashboardQueryServiceTest.kt
  • src/test/kotlin/com/weeth/domain/user/domain/entity/UserTest.kt
  • src/test/kotlin/com/weeth/domain/user/fixture/UserTestFixture.kt
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/kotlin/com/weeth/domain/user/application/dto/request/UpdateUserProfileRequest.kt
  • src/main/kotlin/com/weeth/domain/user/application/dto/response/UserProfileResponse.kt

Comment thread src/main/kotlin/com/weeth/domain/user/domain/entity/User.kt
Comment on lines +78 to +80
@Column(length = 500)
var profileImageUrl: String? = profileImageUrl?.trim()?.takeIf { it.isNotBlank() }
private set
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

profileImageUrl도 컬럼 길이 검증이 필요합니다.

지금은 trim/null 정규화만 하고 500자 제한은 강제하지 않아서, 너무 긴 URL이 들어오면 스키마 제약을 애플리케이션 레벨에서 보장하지 못합니다. 생성/수정 경로가 같은 정규화 함수를 타면서 길이도 같이 검증하도록 맞춰두는 편이 안전합니다.

🛡️ 제안
     `@Column`(length = 500)
-    var profileImageUrl: String? = profileImageUrl?.trim()?.takeIf { it.isNotBlank() }
+    var profileImageUrl: String? = normalizeProfileImageUrl(profileImageUrl)
         private set
@@
     fun updateProfileImageUrl(url: String?) {
-        this.profileImageUrl = url?.trim()?.takeIf { it.isNotBlank() }
+        this.profileImageUrl = normalizeProfileImageUrl(url)
     }
+
+    private fun normalizeProfileImageUrl(url: String?): String? =
+        url?.trim()?.takeIf { it.isNotBlank() }?.also {
+            require(it.length <= 500) { "프로필 이미지 URL은 500자 이하여야 합니다." }
+        }

Also applies to: 128-130

🤖 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 78
- 80, The profileImageUrl property currently only trims/null-normalizes but does
not enforce the 500-char column length, so long values may violate DB
constraints; update the normalization logic used during construction/update (the
code paths or function that currently sets profileImageUrl) to also enforce
length by truncating or rejecting strings beyond 500 chars (e.g., apply .takeIf
{ it.length <= 500 } or .take(500) before assignment) and ensure the same change
is applied to the other occurrences mentioned (lines referenced around
profileImageUrl and the similar block at 128-130) so application-level
validation matches the `@Column`(length = 500) constraint.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/test/kotlin/com/weeth/domain/club/fixture/ClubMemberTestFixture.kt (1)

25-45: 일관성을 위해 다른 팩토리 메서드에도 id 파라미터 추가 고려

createActiveMember에는 id 파라미터가 추가되었지만, createWaitingMembercreateAdminMember에는 없습니다. 향후 테스트에서 이 메서드들로 생성된 엔티티의 ID가 필요할 수 있으므로, 동일한 패턴을 적용하는 것을 고려해 주세요.

♻️ 제안된 변경 사항
 fun createWaitingMember(
+    id: Long = 0L,
     club: Club = ClubTestFixture.createClub(),
     user: User = UserTestFixture.createWaitingUser1(),
 ): ClubMember =
     ClubMember(
         club = club,
         user = user,
         memberStatus = MemberStatus.WAITING,
         memberRole = MemberRole.USER,
-    )
+    ).also { if (id != 0L) ReflectionTestUtils.setField(it, "id", id) }

 fun createAdminMember(
+    id: Long = 0L,
     club: Club = ClubTestFixture.createClub(),
     user: User = UserTestFixture.createAdmin(),
 ): ClubMember =
     ClubMember(
         club = club,
         user = user,
         memberStatus = MemberStatus.ACTIVE,
         memberRole = MemberRole.ADMIN,
-    )
+    ).also { if (id != 0L) ReflectionTestUtils.setField(it, "id", id) }
🤖 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/fixture/ClubMemberTestFixture.kt`
around lines 25 - 45, Add an optional id parameter to createWaitingMember and
createAdminMember to match createActiveMember (e.g. id: Long? = null) and pass
it into the ClubMember constructor (id = id) so all test factory methods support
supplying an explicit entity id for consistency in tests; update the function
signatures for createWaitingMember and createAdminMember and their ClubMember
construction to include the id parameter.
src/main/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubMemberUsecase.kt (1)

87-92: 크로스 도메인 읽기에 Reader 인터페이스 사용 권장

현재 fileRepository.findAllByOwnerTypeAndOwnerIdAndStatus()는 file 도메인에서 직접 읽기 작업을 수행하고 있습니다. 코딩 가이드라인에 따르면 "크로스 도메인 읽기는 Reader 인터페이스를 사용해야 한다"고 명시되어 있습니다.

FileReader 인터페이스를 도입하여 크로스 도메인 의존성을 완화하는 것을 권장합니다.

♻️ FileReader 인터페이스 도입 제안
// FileReader 인터페이스 정의 (file 도메인)
interface FileReader {
    fun findAllByOwnerTypeAndOwnerIdAndStatus(
        ownerType: FileOwnerType,
        ownerId: Long,
        status: FileStatus,
    ): List<File>
}
 class ManageClubMemberUsecase(
     private val clubRepository: ClubRepository,
     private val clubMemberRepository: ClubMemberRepository,
     private val userReader: UserReader,
     private val clubMemberPolicy: ClubMemberPolicy,
+    private val fileReader: FileReader,
     private val fileRepository: FileRepository,
     private val fileAccessUrlPort: FileAccessUrlPort,
 ) {
-        fileRepository
+        fileReader
             .findAllByOwnerTypeAndOwnerIdAndStatus(
                 FileOwnerType.CLUB_MEMBER_PROFILE,
                 member.id,
                 FileStatus.UPLOADED,
             ).forEach { it.markDeleted() }

As per coding guidelines: "Cross-domain reads must use Reader interfaces; cross-domain writes call Repository directly"

🤖 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 87 - 92, Replace the direct cross-domain read call to
fileRepository.findAllByOwnerTypeAndOwnerIdAndStatus with a FileReader
interface: add a constructor dependency (e.g., fileReader: FileReader) to
ManageClubMemberUsecase, call
fileReader.findAllByOwnerTypeAndOwnerIdAndStatus(FileOwnerType.CLUB_MEMBER_PROFILE,
member.id, FileStatus.UPLOADED) to fetch files, and then continue to
markDeleted() on each returned File; keep using fileRepository only for
repository writes if needed. Ensure FileReader defines fun
findAllByOwnerTypeAndOwnerIdAndStatus(ownerType: FileOwnerType, ownerId: Long,
status: FileStatus): List<File> and update usages in ManageClubMemberUsecase to
use fileReader for reads.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@src/main/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubMemberUsecase.kt`:
- Around line 87-92: Replace the direct cross-domain read call to
fileRepository.findAllByOwnerTypeAndOwnerIdAndStatus with a FileReader
interface: add a constructor dependency (e.g., fileReader: FileReader) to
ManageClubMemberUsecase, call
fileReader.findAllByOwnerTypeAndOwnerIdAndStatus(FileOwnerType.CLUB_MEMBER_PROFILE,
member.id, FileStatus.UPLOADED) to fetch files, and then continue to
markDeleted() on each returned File; keep using fileRepository only for
repository writes if needed. Ensure FileReader defines fun
findAllByOwnerTypeAndOwnerIdAndStatus(ownerType: FileOwnerType, ownerId: Long,
status: FileStatus): List<File> and update usages in ManageClubMemberUsecase to
use fileReader for reads.

In `@src/test/kotlin/com/weeth/domain/club/fixture/ClubMemberTestFixture.kt`:
- Around line 25-45: Add an optional id parameter to createWaitingMember and
createAdminMember to match createActiveMember (e.g. id: Long? = null) and pass
it into the ClubMember constructor (id = id) so all test factory methods support
supplying an explicit entity id for consistency in tests; update the function
signatures for createWaitingMember and createAdminMember and their ClubMember
construction to include the id parameter.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4144376a-ddda-402b-82f1-d0519003618d

📥 Commits

Reviewing files that changed from the base of the PR and between 259c71b and 9628164.

📒 Files selected for processing (18)
  • src/main/kotlin/com/weeth/domain/club/application/dto/request/UpdateMemberBioRequest.kt
  • src/main/kotlin/com/weeth/domain/club/application/dto/response/ClubMemberProfileResponse.kt
  • src/main/kotlin/com/weeth/domain/club/application/mapper/ClubMapper.kt
  • src/main/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubMemberUsecase.kt
  • src/main/kotlin/com/weeth/domain/club/domain/entity/ClubMember.kt
  • src/main/kotlin/com/weeth/domain/club/presentation/ClubController.kt
  • src/main/kotlin/com/weeth/domain/club/presentation/ClubResponseCode.kt
  • src/main/kotlin/com/weeth/domain/dashboard/application/dto/response/DashboardMyInfoResponse.kt
  • src/main/kotlin/com/weeth/domain/dashboard/application/mapper/DashboardMapper.kt
  • src/main/kotlin/com/weeth/domain/file/domain/enums/FileOwnerType.kt
  • src/main/kotlin/com/weeth/domain/user/application/usecase/command/SocialLoginUseCase.kt
  • src/main/kotlin/com/weeth/domain/user/domain/vo/SocialAuthResult.kt
  • src/main/kotlin/com/weeth/domain/user/infrastructure/AppleSocialAuthAdapter.kt
  • src/main/kotlin/com/weeth/domain/user/infrastructure/KakaoSocialAuthAdapter.kt
  • src/main/kotlin/com/weeth/global/auth/kakao/dto/KakaoProfile.kt
  • src/test/kotlin/com/weeth/domain/club/application/usecase/command/ManageClubMemberUseCaseTest.kt
  • src/test/kotlin/com/weeth/domain/club/fixture/ClubMemberTestFixture.kt
  • src/test/kotlin/com/weeth/domain/user/application/usecase/command/SocialLoginUseCaseTest.kt
💤 Files with no reviewable changes (5)
  • src/main/kotlin/com/weeth/domain/user/infrastructure/AppleSocialAuthAdapter.kt
  • src/test/kotlin/com/weeth/domain/user/application/usecase/command/SocialLoginUseCaseTest.kt
  • src/main/kotlin/com/weeth/domain/user/domain/vo/SocialAuthResult.kt
  • src/main/kotlin/com/weeth/domain/user/infrastructure/KakaoSocialAuthAdapter.kt
  • src/main/kotlin/com/weeth/global/auth/kakao/dto/KakaoProfile.kt
✅ Files skipped from review due to trivial changes (2)
  • src/main/kotlin/com/weeth/domain/file/domain/enums/FileOwnerType.kt
  • src/main/kotlin/com/weeth/domain/club/application/dto/request/UpdateMemberBioRequest.kt
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/main/kotlin/com/weeth/domain/club/application/dto/response/ClubMemberProfileResponse.kt
  • src/main/kotlin/com/weeth/domain/club/presentation/ClubResponseCode.kt
  • src/main/kotlin/com/weeth/domain/dashboard/application/dto/response/DashboardMyInfoResponse.kt
  • src/main/kotlin/com/weeth/domain/club/domain/entity/ClubMember.kt
  • src/main/kotlin/com/weeth/domain/club/presentation/ClubController.kt

import io.swagger.v3.oas.annotations.media.Schema
import jakarta.validation.constraints.Size

data class UpdateMemberBioRequest(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dto 이름이 bio인 이유가 있을까요???

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아 요즘은 자기소개를 bio라고 하는군요

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

마자요 ㅎㅎㅎㅎ

return CommonResponse.success(ClubResponseCode.MEMBER_FIND_ME_SUCCESS, meInfo)
}

@PatchMapping("/{clubId}/members/me/profile-image")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

동아리 정보 수정 관련해서 구현하다보니 프사 삭제가 약간 애매하더라구요 null이면 PATCH 성격에 따라서 수정을 안하는 방향으로 해야하는데, 그럼 사진을 삭제할 방법이 없어서 저는 별도 API를 떼긴 했습니다!

return CommonResponse.success(ClubResponseCode.MEMBER_PROFILE_IMAGE_UPDATED_SUCCESS)
}

@PatchMapping("/{clubId}/members/me/bio")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아직 뷰가 안나오긴 했지만, Weeth에서는 정보 수정을 여러 API로 나누는게 아니라 여러 정보를 한 API로 받되, PATCH에 맞게 변경된 값만 저장하는 식으로 관리하고 있어요!
그래서 API를 나누기 보단 "동아리 정보 수정" API처럼 null인 경우는 수정 안함, 값이 있는 경우는 수정 이렇게 API를 합쳐야할 것 같아요!

return CommonResponse.success(UserResponseCode.USER_TERMS_AGREE_SUCCESS)
}

@PutMapping("/profile-image")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 API는 현재 필요 없는 것 아닌가용??

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아 그러네용 제거해두겠습니다!

@hyxklee hyxklee merged commit 1ce5373 into dev Mar 23, 2026
2 checks passed
@hyxklee hyxklee deleted the feat/WTH-208-사용자-프로필-기능-추가 branch March 23, 2026 02:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 새로운 기능 추가 🔨 Refactor 코드 구조 개선 및 리팩토링

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants