[feature] 모집글 수정 시 최근 업데이트 일자를 보여준다#898
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning
|
| Cohort / File(s) | 변경 내용 |
|---|---|
엔티티: 모집정보 필드 추가backend/src/main/java/moadong/club/entity/ClubRecruitmentInformation.java |
private LocalDateTime lastModifiedDate 필드 추가 및 public void updateLastModifiedDate() 메서드 추가(현재 시간으로 설정). |
서비스: 업데이트 시각 갱신 호출backend/src/main/java/moadong/club/service/ClubProfileService.java |
updateClubRecruitmentInfo 내부에서 Recruitment 상태 계산 후 club.getClubRecruitmentInformation().updateLastModifiedDate() 호출 추가. |
DTO: 출력 필드 추가backend/src/main/java/moadong/club/payload/dto/ClubDetailedResult.java |
응답 필드로 String lastModifiedDate 추가; of(...)/빌더에서 lastModifiedDate를 "yyyy.MM.dd HH:mm" 형식으로 포맷해 설정하도록 추가. |
테스트: 단위 테스트 추가backend/src/test/java/moadong/club/service/ClubProfileServiceDateTest.java |
모집글 업데이트 시 lastModifiedDate가 설정되는지 검증하는 테스트 클래스 추가(리포지토리 모킹, RecruitmentStateCalculator 정적 모킹 사용). |
테스트 픽스처 확장backend/src/test/java/moadong/fixture/ClubRequestFixture.java |
public static ClubRecruitmentInfoUpdateRequest defaultRequest() 헬퍼 메서드 추가 (LocalDateTime 사용). |
소소한 엔티티 변경backend/src/main/java/moadong/club/entity/Club.java |
@Version 어노테이션 제거 및 불필요한 임포트/포맷 정리(버전 필드는 유지되나 어노테이션 제거). |
Sequence Diagram(s)
sequenceDiagram
participant Client
participant ClubProfileService
participant RecruitmentStateCalculator as Calculator
participant ClubEntity as Club
participant Repo as ClubRepository
Client->>ClubProfileService: updateClubRecruitmentInfo(request, user)
ClubProfileService->>Calculator: RecruitmentStateCalculator.calculate(club, request)
ClubProfileService->>Club: club.updateRecruitmentInfo(requestData)
ClubProfileService->>Club: club.getClubRecruitmentInformation().updateLastModifiedDate()
ClubProfileService->>Repo: clubRepository.save(club)
Repo-->>ClubProfileService: savedClub
ClubProfileService-->>Client: 응답 (업데이트 완료)
Estimated code review effort
🎯 3 (Moderate) | ⏱️ ~20 minutes
- 집중 검토 포인트:
lastModifiedDate의 시간대(Zone) 일관성 및 포맷 위치(DTO vs 서비스) 확인- 영속성 저장 흐름(변경감지 vs 명시적 save)에서 값이 정상 저장되는지 검증 (
ClubRepository관련) - 시간 민감 테스트 안정성(±1초 검사)이 CI 환경에서 신뢰할 수 있는지 확인
Club.java에서@Version제거가 낙관적 락 동작에 미치는 영향
Possibly related issues
- MOA-393: 모집글 수정 시 최근 업데이트 일자를 보여준다 — 본 PR은 모집글 수정 시점의 최근 업데이트 일자 저장·노출을 구현함
- [feature] MOA-393 모집글 수정 시 최근 업데이트 일자를 보여준다 #893 — UI에 노출할 last-updated 구현 관련 이슈와 기능적으로 일치함
Possibly related PRs
- [feature] 상시 모집 상태 추가 및 모집 정보 변경 시 반영되도록 변경 #485 — 동일 서비스 메서드(
updateClubRecruitmentInfo) 관련 변경으로 연관성 높음 - [Release] BE v1.0.6 배포 #675 —
ClubDetailedResult관련 DTO 변경과 중첩될 수 있음 - [feature] 액세스 토큰 및 리프레시 토큰의 만료 시간을 수정하고, 로그인, 관리자 계정 관련의 동시성 문제를 해결한다 #713 —
Club엔티티의 버전/영속성 관련 변경(어노테이션 변경)과 충돌 가능성 존재
Suggested labels
✅ Test
Suggested reviewers
- PororoAndFriends
- lepitaaar
- Zepelown
Pre-merge checks and finishing touches
❌ Failed checks (1 warning)
| Check name | Status | Explanation | Resolution |
|---|---|---|---|
| Docstring Coverage | Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. | You can run @coderabbitai generate docstrings to improve docstring coverage. |
✅ Passed checks (4 passed)
| Check name | Status | Explanation |
|---|---|---|
| Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. |
| Title check | ✅ Passed | The PR title clearly describes the main feature: displaying the last update date when recruitment posts are edited. |
| Linked Issues check | ✅ Passed | The PR implements the core requirement from MOA-393: adding and displaying the last modified date for recruitment information when updated. |
| Out of Scope Changes check | ✅ Passed | All changes are directly related to the linked issue MOA-393; no out-of-scope modifications detected in the codebase. |
✨ Finishing touches
- 📝 Generate docstrings
🧪 Generate unit tests (beta)
- Create PR with unit tests
- Post copyable unit tests in a comment
- Commit unit tests in branch
feature/#893-show-last-updated-on-post-edit-MOA-393
📜 Recent review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
backend/src/test/java/moadong/club/service/ClubProfileServiceDateTest.java(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- backend/src/test/java/moadong/club/service/ClubProfileServiceDateTest.java
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 @coderabbitai help to get the list of available commands and usage tips.
Test Results69 tests 66 ✅ 15s ⏱️ Results for commit 8898972. ♻️ This comment has been updated with latest results. |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (4)
backend/src/main/java/moadong/club/service/ClubProfileService.java (1)
38-50:@Transactional어노테이션 누락
updateClubInfo메서드(라인 30)에는@Transactional이 있지만,updateClubRecruitmentInfo에는 없습니다. 일관성을 위해 추가를 권장합니다.또한,
LocalDateTime.now()는 시스템 기본 타임존을 사용하는 반면,RecruitmentStateCalculator는ZonedDateTime.now(ZoneId.of("Asia/Seoul"))을 사용합니다. 타임존 일관성을 위해 명시적으로 지정하는 것이 좋습니다.+ @Transactional public void updateClubRecruitmentInfo(ClubRecruitmentInfoUpdateRequest request, CustomUserDetails user) { Club club = clubRepository.findClubByUserId(user.getId()) .orElseThrow(() -> new RestApiException(ErrorCode.CLUB_NOT_FOUND)); club.update(request); RecruitmentStateCalculator.calculate( club, club.getClubRecruitmentInformation().getRecruitmentStart(), club.getClubRecruitmentInformation().getRecruitmentEnd() ); - club.setLastModified(LocalDateTime.now()); + club.setLastModified(LocalDateTime.now(java.time.ZoneId.of("Asia/Seoul"))); clubRepository.save(club); }backend/src/main/java/moadong/club/entity/Club.java (1)
54-56: 중복된@Getter어노테이션 제거클래스 레벨에 이미
@Getter가 선언되어 있으므로(라인 30), 필드 레벨의@Getter는 중복입니다.@Setter - @Getter private LocalDateTime lastModified;backend/src/main/java/moadong/club/payload/dto/ClubDetailedResult.java (1)
40-48:DateTimeFormatter중복 생성동일한 패턴(
"yyyy.MM.dd HH:mm")으로DateTimeFormatter가 두 번 생성됩니다 (라인 40, 46). 상수로 추출하면 코드 중복을 줄이고 성능을 개선할 수 있습니다.+ private static final DateTimeFormatter DATE_TIME_FORMATTER = + DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm"); + public static ClubDetailedResult of(Club club, List<ClubSearchResult> recommendClubs) { String period = "미정"; ClubRecruitmentInformation clubRecruitmentInformation = club.getClubRecruitmentInformation(); if (clubRecruitmentInformation.hasRecruitmentPeriod()) { - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm"); - period = clubRecruitmentInformation.getRecruitmentStart().format(formatter) + " ~ " - + clubRecruitmentInformation.getRecruitmentEnd().format(formatter); + period = clubRecruitmentInformation.getRecruitmentStart().format(DATE_TIME_FORMATTER) + " ~ " + + clubRecruitmentInformation.getRecruitmentEnd().format(DATE_TIME_FORMATTER); } String lastModified = ""; if (club.getLastModified() != null) { - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm"); - lastModified = club.getLastModified().format(formatter); + lastModified = club.getLastModified().format(DATE_TIME_FORMATTER); }backend/src/test/java/moadong/club/service/ClubProfileServiceDateTest.java (1)
59-68:clubRepository.save()호출 검증 추가 권장
lastModified가 설정된 후 실제로 저장되는지 검증하면 테스트 신뢰성이 높아집니다.//WHEN clubProfileService.updateClubRecruitmentInfo(request, customUserDetails); //THEN assertNotNull(club.getLastModified()); //1초 전후 차이로 살펴보기 LocalDateTime now = LocalDateTime.now(); assertTrue(club.getLastModified().isAfter(now.minusSeconds(1))); assertTrue(club.getLastModified().isBefore(now.plusSeconds(1))); + + // 저장 호출 검증 + org.mockito.Mockito.verify(clubRepository).save(club); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (5)
backend/src/main/java/moadong/club/entity/Club.java(2 hunks)backend/src/main/java/moadong/club/payload/dto/ClubDetailedResult.java(3 hunks)backend/src/main/java/moadong/club/service/ClubProfileService.java(2 hunks)backend/src/test/java/moadong/club/service/ClubProfileServiceDateTest.java(1 hunks)backend/src/test/java/moadong/fixture/ClubRequestFixture.java(2 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-05-19T05:45:52.957Z
Learnt from: lepitaaar
Repo: Moadong/moadong PR: 406
File: backend/src/main/java/moadong/club/service/ClubApplyService.java:34-38
Timestamp: 2025-05-19T05:45:52.957Z
Learning: The code duplication between createClubApplication and editClubApplication methods in ClubApplyService.java is acknowledged but will be addressed in a future refactoring, as per the developer's plan.
Applied to files:
backend/src/main/java/moadong/club/service/ClubProfileService.java
📚 Learning: 2025-09-30T05:26:41.788Z
Learnt from: alsdddk
Repo: Moadong/moadong PR: 765
File: backend/src/main/java/moadong/club/service/ClubApplyService.java:431-435
Timestamp: 2025-09-30T05:26:41.788Z
Learning: In the Moadong codebase's club application feature (backend/src/main/java/moadong/club/), multiple ClubApplicationForm entities can have ACTIVE status for the same clubId, semesterYear, and semesterTerm simultaneously. There is no uniqueness constraint requiring only one ACTIVE form per semester.
Applied to files:
backend/src/main/java/moadong/club/service/ClubProfileService.javabackend/src/main/java/moadong/club/entity/Club.java
📚 Learning: 2025-08-25T14:43:52.320Z
Learnt from: lepitaaar
Repo: Moadong/moadong PR: 703
File: backend/src/main/java/moadong/club/controller/ClubApplyController.java:84-84
Timestamp: 2025-08-25T14:43:52.320Z
Learning: In the Moadong codebase, questionId and clubId are equivalent identifiers that represent the same entity. The ClubApplicationRepository.findAllByIdInAndQuestionId method correctly uses clubId as the questionId parameter for filtering club applications.
Applied to files:
backend/src/main/java/moadong/club/service/ClubProfileService.java
🧬 Code graph analysis (1)
backend/src/test/java/moadong/club/service/ClubProfileServiceDateTest.java (4)
backend/src/main/java/moadong/club/util/RecruitmentStateCalculator.java (1)
RecruitmentStateCalculator(15-78)backend/src/test/java/moadong/fixture/ClubRequestFixture.java (1)
ClubRequestFixture(12-64)backend/src/test/java/moadong/fixture/UserFixture.java (1)
UserFixture(9-33)backend/src/test/java/moadong/unit/club/ClubProfileServiceTest.java (1)
UnitTest(23-57)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: test
🔇 Additional comments (1)
backend/src/test/java/moadong/fixture/ClubRequestFixture.java (1)
27-36: LGTM!테스트 픽스처로서 적절하게 구현되었습니다.
defaultRequest()메서드가 테스트에서 일관된 요청 객체 생성을 돕습니다.
backend/src/test/java/moadong/club/service/ClubProfileServiceDateTest.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
backend/src/main/java/moadong/club/entity/Club.java (2)
54-55: public setter 노출을 제한하여 캡슐화를 개선하세요.현재
@Setter어노테이션으로 인해setLastModified(LocalDateTime)public 메서드가 생성되어, 외부 코드에서 임의로 타임스탬프를 수정할 수 있습니다. 이는 다음과 같은 문제를 야기할 수 있습니다:
- 비즈니스 로직과 무관하게 타임스탬프가 변경될 수 있음
- 데이터 무결성 보장이 어려움
다음 방법 중 하나를 고려해보세요:
방법 1 (권장):
@Setter를 제거하고 엔티티의update()메서드 내에서 타임스탬프를 직접 설정- @Setter private LocalDateTime lastModified; public void update(ClubRecruitmentInfoUpdateRequest request) { clubRecruitmentInformation.updateDescription(request); + this.lastModified = LocalDateTime.now(); }방법 2: Spring Data MongoDB의 auditing 기능 활용 (모든 업데이트에 반영하려는 경우)
- @Setter + @LastModifiedDate private LocalDateTime lastModified;Note: 현재 구현에서는 모집 정보 수정 시에만 업데이트하려는 의도로 보이므로 방법 1이 더 적합합니다.
57-91: 생성자에서 lastModified 초기화를 고려하세요.현재 모든 생성자에서
lastModified필드가 초기화되지 않아 새로운 인스턴스는 null 값을 가집니다. 모집 정보 업데이트 추적이 목적이라면 현재 구현도 충분하지만, 일관성을 위해 다음을 고려할 수 있습니다:
- 생성 시점의 타임스탬프로 초기화 (필요한 경우)
- 또는 null을 명시적으로 문서화
예시 (선택사항):
public Club() { this.name = ""; this.category = ""; this.division = ""; this.state = ClubState.UNAVAILABLE; this.clubRecruitmentInformation = ClubRecruitmentInformation.builder().build(); + this.lastModified = null; // 명시적으로 null 표시, 또는 LocalDateTime.now()로 초기화 }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
backend/src/main/java/moadong/club/entity/Club.java(2 hunks)backend/src/test/java/moadong/club/service/ClubProfileServiceDateTest.java(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- backend/src/test/java/moadong/club/service/ClubProfileServiceDateTest.java
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-30T05:26:41.788Z
Learnt from: alsdddk
Repo: Moadong/moadong PR: 765
File: backend/src/main/java/moadong/club/service/ClubApplyService.java:431-435
Timestamp: 2025-09-30T05:26:41.788Z
Learning: In the Moadong codebase's club application feature (backend/src/main/java/moadong/club/), multiple ClubApplicationForm entities can have ACTIVE status for the same clubId, semesterYear, and semesterTerm simultaneously. There is no uniqueness constraint requiring only one ACTIVE form per semester.
Applied to files:
backend/src/main/java/moadong/club/entity/Club.java
🔇 Additional comments (1)
backend/src/main/java/moadong/club/entity/Club.java (1)
3-3: LGTM!새로운
lastModified필드를 위한 필수 import가 올바르게 추가되었습니다.Also applies to: 13-13
lepitaaar
left a comment
There was a problem hiding this comment.
수고하셨습니다! 기능개발하면서 테스트코드까지 작성하신거 좋네요~ 아래 리뷰들 확인해주시면 감사하겠습니다
backend/src/test/java/moadong/club/service/ClubProfileServiceDateTest.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
backend/src/main/java/moadong/club/entity/ClubRecruitmentInformation.java (1)
70-70: lastModifiedDate 시간대 처리 일관성은 선택적으로 정리해볼 수 있습니다
updateLastModifiedDate()에서LocalDateTime.now()를 그대로 사용하는 것은 기능적으로 문제 없지만, 이 엔티티가 모집기간에 대해서는ZoneId.of("Asia/Seoul")를 명시적으로 사용하는 만큼, 최근 수정 일자도 동일한 기준(예: 서울 타임존 기준의 now 혹은 UTC 고정)으로 맞춰두면 장기적으로 혼동을 줄일 수 있습니다.예를 들어 KST 고정이 의도라면, 다음처럼 구현하는 것도 한 가지 방법입니다 (참고용):
public void updateLastModifiedDate() { setLastModifiedDate(ZonedDateTime.now(ZoneId.of("Asia/Seoul")).toLocalDateTime()); }지금 구현을 그대로 두어도 동작에는 문제 없고, 운영 환경 타임존이 고정이라면 필수 변경은 아닙니다.
Also applies to: 128-134
backend/src/main/java/moadong/club/payload/dto/ClubDetailedResult.java (1)
32-32: 최근 수정 일자 포맷 및 매핑이 요구사항에 잘 맞습니다
ClubDetailedResult에lastModifiedDate필드를 추가하고,ClubRecruitmentInformation.lastModifiedDate가 있을 때만"yyyy.MM.dd HH:mm"포맷의 문자열로 변환해서 내려주는 흐름이 자연스럽고, null일 때 빈 문자열로 처리하는 방식도 기존 DTO 패턴과 일관적입니다. 빌더에.lastModifiedDate(lastModifiedDate)로 잘 연결된 것도 확인했습니다.추가로, 선택사항이지만
DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm")가recruitmentPeriod와lastModifiedDate에서 반복되고 있으니, 공통 상수로 추출해두면 향후 포맷 변경 시 관리가 조금 더 수월해질 것 같습니다.Also applies to: 45-49, 84-84
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (4)
backend/src/main/java/moadong/club/entity/Club.java(0 hunks)backend/src/main/java/moadong/club/entity/ClubRecruitmentInformation.java(3 hunks)backend/src/main/java/moadong/club/payload/dto/ClubDetailedResult.java(3 hunks)backend/src/main/java/moadong/club/service/ClubProfileService.java(2 hunks)
💤 Files with no reviewable changes (1)
- backend/src/main/java/moadong/club/entity/Club.java
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-05-19T05:45:52.957Z
Learnt from: lepitaaar
Repo: Moadong/moadong PR: 406
File: backend/src/main/java/moadong/club/service/ClubApplyService.java:34-38
Timestamp: 2025-05-19T05:45:52.957Z
Learning: The code duplication between createClubApplication and editClubApplication methods in ClubApplyService.java is acknowledged but will be addressed in a future refactoring, as per the developer's plan.
Applied to files:
backend/src/main/java/moadong/club/service/ClubProfileService.java
📚 Learning: 2025-03-19T05:18:07.818Z
Learnt from: seongwon030
Repo: Moadong/moadong PR: 195
File: frontend/src/pages/AdminPage/AdminPage.tsx:7-7
Timestamp: 2025-03-19T05:18:07.818Z
Learning: AdminPage.tsx에서 현재 하드코딩된 클럽 ID('67d2e3b9b15c136c6acbf20b')는 로그인 기능 구현 후 동적으로 가져오는 방식으로 수정될 예정입니다.
Applied to files:
backend/src/main/java/moadong/club/service/ClubProfileService.java
📚 Learning: 2025-09-30T05:26:41.788Z
Learnt from: alsdddk
Repo: Moadong/moadong PR: 765
File: backend/src/main/java/moadong/club/service/ClubApplyService.java:431-435
Timestamp: 2025-09-30T05:26:41.788Z
Learning: In the Moadong codebase's club application feature (backend/src/main/java/moadong/club/), multiple ClubApplicationForm entities can have ACTIVE status for the same clubId, semesterYear, and semesterTerm simultaneously. There is no uniqueness constraint requiring only one ACTIVE form per semester.
Applied to files:
backend/src/main/java/moadong/club/service/ClubProfileService.java
📚 Learning: 2025-08-25T14:43:52.320Z
Learnt from: lepitaaar
Repo: Moadong/moadong PR: 703
File: backend/src/main/java/moadong/club/controller/ClubApplyController.java:84-84
Timestamp: 2025-08-25T14:43:52.320Z
Learning: In the Moadong codebase, questionId and clubId are equivalent identifiers that represent the same entity. The ClubApplicationRepository.findAllByIdInAndQuestionId method correctly uses clubId as the questionId parameter for filtering club applications.
Applied to files:
backend/src/main/java/moadong/club/service/ClubProfileService.java
🔇 Additional comments (1)
backend/src/main/java/moadong/club/service/ClubProfileService.java (1)
3-3: 모집정보 수정 시점에 lastModifiedDate 갱신 위치 적절합니다
updateClubRecruitmentInfo안에서club.update(request)이후에club.getClubRecruitmentInformation().updateLastModifiedDate()를 호출해서, 모집정보가 수정될 때마다 일관되게 최근 수정 일자가 갱신되도록 연결된 점 좋습니다. 기존 리뷰 코멘트에서 제안된 형태대로 잘 반영된 것으로 보이며, 서비스 레이어 책임과도 잘 맞습니다.Also applies to: 48-48
#️⃣연관된 이슈
📝작업 내용
📝작업 내용
중점적으로 리뷰받고 싶은 부분(선택)
논의하고 싶은 부분(선택)
🫡 참고사항
Summary by CodeRabbit
새로운 기능
테스트
✏️ Tip: You can customize this high-level summary in your review settings.