Feat/water sunlight#66
Conversation
Walkthrough소유자/친구 물주기 흐름을 분리하고 시간·일자 제약과 로그를 도입했습니다. Garden에 소유자/친구 물주기 타임스탬프를 추가하고, 친구 물주기 전용 로그 엔티티 및 레포지토리를 추가하여 일별·정원별 중복을 검사합니다. 컨트롤러는 단일 물주기 엔드포인트를 두 개로 분리했습니다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant C as 클라이언트
participant Ctrl as GardenController
participant Svc as GardenService
participant UR as UserRepository
participant FLR as FriendWateringLogRepository
participant G as Garden
rect rgba(230,245,255,0.6)
note over C,Ctrl: 내 정원 물주기 (POST /{gardenId}/mywater)
C->>Ctrl: POST /{gardenId}/mywater (userId, gardenId)
Ctrl->>Svc: waterGarden(userId, gardenId)
Svc->>G: 조회 및 lastWateredByOwnerAt 확인
alt 8시간 이내
Svc-->>Ctrl: 오류 반환
else 허용
Svc->>G: recordOwnerWateringTime()
Svc-->>Ctrl: 200 OK
end
end
rect rgba(240,255,240,0.6)
note over C,Ctrl: 친구 정원 물주기 (POST /{gardenId}/friendwater)
C->>Ctrl: POST /{gardenId}/friendwater (userId, gardenId)
Ctrl->>Svc: waterGarden(userId, gardenId)
Svc->>UR: 사용자 검증/조회
Svc->>FLR: countByWaterGiverAndWateredAtAfter(user, startOfDay)
alt 일일 3회 초과
Svc-->>Ctrl: 오류 반환
else 허용
Svc->>FLR: existsByWaterGiverAndWateredGardenAndWateredAtAfter(user, garden, startOfDay)
alt 이미 해당 정원에 물줌
Svc-->>Ctrl: 오류 반환
else 최초
Svc->>G: recordFriendWateringTime()
Svc->>FLR: save(FriendWateringLog)
Svc-->>Ctrl: 200 OK
end
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. 📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
|
🚨 PR 본문이 비어있습니다! 아래 템플릿을 복사하여 PR 내용을 작성해주세요. 📝 개요
💻 작업 내용
✅ PR 체크리스트
🔗 관련 이슈
스크린샷 (선택)
|
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/main/java/com/example/cp_main_be/domain/garden/garden/domain/Garden.java (1)
17-17: Lombok @builder 중복 사용으로 인한 컴파일 충돌 위험클래스 레벨(@builder)과 특정 생성자(@builder)가 동시에 선언되어 있어 동일한
builder()팩토리 메서드가 중복 생성될 수 있습니다. 이는 Lombok 처리 단계에서 컴파일 에러로 이어집니다.한쪽만 유지하거나 메서드명을 분리하세요.
-@Builder public class Garden { @@ - @Builder public Garden(User user, Integer slotNumber, GardenBackground gardenBackground, Avatar avatar) {또는
- @Builder + @Builder(builderMethodName = "createGarden") public Garden(User user, Integer slotNumber, GardenBackground gardenBackground, Avatar avatar) {Also applies to: 63-64
🧹 Nitpick comments (9)
src/main/java/com/example/cp_main_be/domain/garden/garden/domain/Garden.java (1)
81-87: recordFriendWateringTime()가 서비스 로직에서 사용되지 않음친구 물주기 시각을 기록하는 public 메서드를 추가하셨지만, GardenService의 친구 물주기 경로에서 호출되지 않습니다. 도메인 모델에만 시각이 남지 않아 API 응답/관리 화면에서 “최근 친구 물주기 시각”을 노출하려 할 때 일관성이 깨질 수 있습니다.
아래처럼 서비스에서 호출을 추가해 주세요(서비스 파일 제안 diff도 함께 남깁니다).
// GardenService.java - 친구 물주기 성공 분기 직후 - userService.addExperience(actorId, WATERING_POINTS); - garden.increaseWaterCount(); + userService.addExperience(actorId, WATERING_POINTS); + garden.increaseWaterCount(); + garden.recordFriendWateringTime();src/main/java/com/example/cp_main_be/domain/garden/garden/presentation/GardenController.java (3)
33-39: 엔드포인트 의미 구분은 되었으나 서비스 호출이 동일 — 의도 명시성/검증 보강 제안
/mywater가 본인 정원만 대상으로 동작해야 한다면, 컨트롤러 레벨에서 소유자 일치 검증을 선제적으로 수행하거나, 서비스에waterMyGarden(...)같은 명시적 메서드를 두어 의도를 분리하는 편이 가독성과 오용 방지에 유리합니다. 현재는 서비스 내부 분기로 동작은 맞지만, API 의미와 구현이 강하게 결합되지 않아 유지보수성이 떨어집니다.예시:
- gardenService.waterGarden(userId, gardenId); + gardenService.waterMyGarden(userId, gardenId); // 내부에서 소유자 검증/쿨타임 처리
41-47: 친구 물주기 엔드포인트도 동일 서비스 호출 — 오용 방지 검증 보강
/friendwater호출자가 자신의 정원을 대상으로 호출하는 경우에도 현재는 “주인 경로”로 분기되어 진행됩니다(의도상 문제는 없으나 API 의미와 달라질 수 있음). 컨트롤러 레벨에서 “대상 정원이 본인 소유가 아닐 것”을 단언(400/409)하면 API 사용성이 명확해집니다.원치 않으시면 최소한 스웨거 설명에 “본인 정원을 대상으로 호출 시 내부적으로 ‘내 물주기’로 처리됨”을 명시해 주세요.
33-39: 204 No Content 고려성공 시
ApiResponse.success(null)(200)을 반환하고 있는데, 바디가 항상 null이면 204 응답이 의미상 더 적절합니다. 물론 기존 클라이언트가 ApiResponse 래퍼를 기대한다면 그대로 유지하세요.Also applies to: 41-47
src/main/java/com/example/cp_main_be/domain/garden/wateringlog/domain/repository/FriendWateringLogRepository.java (1)
11-15: 일자 경계 상·하한 모두를 파라미터화하는 쿼리 시그니처 고려현재는 “이후(after)” 조건만 있으나, 장기적으로 리포트/검증에 “구간” 기반 조회가 필요해질 수 있습니다. 또한 상한을 명시하면 옵티마이저가 인덱스를 더 잘 활용할 여지가 있습니다.
예시(추가 메서드):
int countByWaterGiverAndWateredAtBetween(User waterGiver, LocalDateTime start, LocalDateTime end); boolean existsByWaterGiverAndWateredGardenAndWateredAtBetween(User waterGiver, Garden garden, LocalDateTime start, LocalDateTime end);src/main/java/com/example/cp_main_be/domain/garden/garden/service/GardenService.java (4)
97-105: 친구 물주기 시 Garden 도메인의 최근 시각 동기화 누락도메인에
recordFriendWateringTime()가 있으므로 상태 일관성을 위해 호출해 두는 편이 좋습니다(별도 UI/응답 노출 대비).FriendWateringLog log = FriendWateringLog.builder().waterGiver(actor).wateredGarden(garden).build(); friendWateringLogRepository.save(log); + garden.recordFriendWateringTime();
34-35: 매직 넘버/하드코딩된 정책 값의 외부화
MAX_FRIEND_WATERING_PER_DAY = 3는 운영 중 조정 가능성이 높은 값입니다.@Value또는 설정 프로퍼티로 외부화하는 것을 권장합니다.Also applies to: 41-41
14-16: 예외 타입 일관성서비스 전반에
IllegalArgumentException/IllegalStateException과 커스텀 예외(CustomApiException)가 혼재되어 있습니다. 글로벌 핸들러 정책에 맞춰 일관된 예외/에러코드로 교체하면 클라이언트 처리와 모니터링이 쉬워집니다.Also applies to: 52-58, 108-122
36-40: 미사용 필드 정리
ApplicationEventPublisher eventPublisher가 더 이상 사용되지 않으면 제거를 고려하세요.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (5)
src/main/java/com/example/cp_main_be/domain/garden/garden/domain/Garden.java(2 hunks)src/main/java/com/example/cp_main_be/domain/garden/garden/presentation/GardenController.java(1 hunks)src/main/java/com/example/cp_main_be/domain/garden/garden/service/GardenService.java(4 hunks)src/main/java/com/example/cp_main_be/domain/garden/wateringlog/domain/FriendWateringLog.java(1 hunks)src/main/java/com/example/cp_main_be/domain/garden/wateringlog/domain/repository/FriendWateringLogRepository.java(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/com/example/cp_main_be/domain/garden/garden/service/GardenService.java (1)
src/main/java/com/example/cp_main_be/domain/member/user/service/UserService.java (1)
Service(20-113)
🔇 Additional comments (1)
src/main/java/com/example/cp_main_be/domain/garden/garden/service/GardenService.java (1)
172-188: “정오(12:00) 기준 일자 경계”가 요구사항인지 확인 필요일반적으로 일별 제한은 자정(00:00) 기준이 많습니다. 정오 기준이 의도라면 OK이며, 그렇지 않다면 오해 소지가 큽니다. 스웨거/에러 메시지에도 기준 시각을 명시해 주세요.
요구사항이 “자정 기준”이라면 아래처럼 바꿉니다.
- private LocalDateTime getStartOfCurrentWateringDay() { - LocalDateTime now = LocalDateTime.now(ZoneId.of("Asia/Seoul")); - LocalDateTime todayNoon = now.toLocalDate().atTime(12, 0); - if (now.isBefore(todayNoon)) { - return todayNoon.minusDays(1); - } else { - return todayNoon; - } - } + private LocalDateTime getStartOfCurrentWateringDay() { + LocalDateTime now = LocalDateTime.now(ZoneId.of("Asia/Seoul")); + return now.toLocalDate().atStartOfDay(); // 00:00 KST + }
| @Column(name = "last_watered_by_owner_at") | ||
| private LocalDateTime lastWateredByOwnerAt; | ||
|
|
||
| @Column(name = "last_watered_by_friend_at") | ||
| private LocalDateTime lastWateredByFriendAt; | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
시간대 불일치로 인한 쿨타임/일자 계산 오류 가능성(LocalDateTime 사용)
lastWateredByOwnerAt, lastWateredByFriendAt를 LocalDateTime(서버 기본 시간대)로 저장하고, 서비스 레이어에서는 Asia/Seoul 기준으로 일자 경계를 계산합니다. 서버 TZ가 KST가 아닐 경우, 8시간 쿨타임/일일 제한 로직과 저장 시점의 시간대가 어긋나 오판정이 발생할 수 있습니다.
아래처럼 KST로 고정 저장(최소 조치) 또는 애그리게이트에서 Instant/OffsetDateTime로 일관 저장(권장)을 고려해주세요.
@@
- public void recordOwnerWateringTime() {
- this.lastWateredByOwnerAt = LocalDateTime.now();
- }
+ public void recordOwnerWateringTime() {
+ this.lastWateredByOwnerAt = LocalDateTime.now(ZoneId.of("Asia/Seoul"));
+ }
@@
- public void recordFriendWateringTime() {
- this.lastWateredByFriendAt = LocalDateTime.now();
- }
+ public void recordFriendWateringTime() {
+ this.lastWateredByFriendAt = LocalDateTime.now(ZoneId.of("Asia/Seoul"));
+ }또는(권장) 필드를 Instant로 변경하고 서비스에서 필요 시점에 atZone(ZoneId.of("Asia/Seoul"))로 변환하는 방식으로 전역 일관성을 확보해 주세요.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @Column(name = "last_watered_by_owner_at") | |
| private LocalDateTime lastWateredByOwnerAt; | |
| @Column(name = "last_watered_by_friend_at") | |
| private LocalDateTime lastWateredByFriendAt; | |
| import java.time.LocalDateTime; | |
| import java.time.ZoneId; | |
| public class Garden { | |
| // ... | |
| public void recordOwnerWateringTime() { | |
| this.lastWateredByOwnerAt = LocalDateTime.now(ZoneId.of("Asia/Seoul")); | |
| } | |
| public void recordFriendWateringTime() { | |
| this.lastWateredByFriendAt = LocalDateTime.now(ZoneId.of("Asia/Seoul")); | |
| } | |
| // ... | |
| } |
🤖 Prompt for AI Agents
In src/main/java/com/example/cp_main_be/domain/garden/garden/domain/Garden.java
around lines 51–56, the two fields use LocalDateTime which can cause timezone
mismatches; change the fields to Instant (or OffsetDateTime) and update JPA
mappings to persist a timezone-aware column (e.g., TIMESTAMP WITH TIME ZONE) so
timestamps are stored in a global instant; update constructors/getters/setters,
any JSON serialization config, and service-layer code to convert to Asia/Seoul
via instant.atZone(ZoneId.of("Asia/Seoul")) when computing cooldowns/daily
boundaries; ensure DB schema and migration reflect the new column type and
adjust any tests that assert on stored timestamps.
| public ResponseEntity<ApiResponse<Void>> waterMyGarden( | ||
| @AuthenticationPrincipal Long userId, @PathVariable Long gardenId) { | ||
| gardenService.waterGarden(userId, gardenId); |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
@AuthenticationPrincipal Long 바인딩 확인 필요
프로젝트 전반에 Long 타입 Principal을 주입하는 설정이 일관적으로 적용돼 있어야 합니다. DTO/도메인 User 주입 방식과 혼용되면 SecurityContext 설정에 따라 NPE/캐스팅 오류가 날 수 있습니다.
다음 스크립트로 컨트롤러 전반의 사용 현황을 확인해 주세요. 일관성이 깨진 곳이 있으면 통일하는 것을 권장합니다.
Also applies to: 43-46
🏁 Script executed:
#!/bin/bash
# 컨트롤러에서 @AuthenticationPrincipal 사용 패턴 확인
rg -n --type=java '@AuthenticationPrincipal' -C2Length of output: 29284
@AuthenticationPrincipal 바인딩 타입 일관화 필요
프로젝트 전반에서 @AuthenticationPrincipal로 주입하는 Principal 타입이 혼용되어 있어 SecurityContext 설정에 따라 NPE나 캐스팅 오류가 발생할 수 있습니다. 다음 위치를 확인하고 하나의 타입으로 통일해주세요.
• 주입 타입별 사용 현황
@AuthenticationPrincipal User user(대다수 컨트롤러)@AuthenticationPrincipal Long userId
• src/main/java/com/example/cp_main_be/domain/reports/presentation/ReportController.java:27
• src/main/java/com/example/cp_main_be/domain/garden/garden/presentation/GardenController.java:36, 44, 52@AuthenticationPrincipal UserDetails userDetails
• src/main/java/com/example/cp_main_be/domain/delivery/presentation/DeliveryController.java:26
권장 조치
- Principal DTO/도메인(
User또는UserDetails) 중 하나로 통일 - ID가 필요한 경우
user.getId()호출로 추출
| if (garden.getLastWateredByOwnerAt() != null | ||
| && garden.getLastWateredByOwnerAt().plusHours(8).isAfter(LocalDateTime.now())) { | ||
| throw new IllegalStateException("아직 물을 줄 수 없습니다. 8시간이 지나야 가능합니다."); | ||
| } |
There was a problem hiding this comment.
8시간 쿨타임 판단의 시간대 불일치
여기서는 LocalDateTime.now()(서버 TZ)를 사용하지만, 친구 물주기 일자 경계는 Asia/Seoul을 사용합니다. 동일 이벤트(물주기)에 서로 다른 시간대 규칙을 적용하면 경계 시각에서 불일치가 발생할 수 있습니다.
KST 기준으로 통일하거나, 전역적으로 Instant를 사용해 비교하세요.
- if (garden.getLastWateredByOwnerAt() != null
- && garden.getLastWateredByOwnerAt().plusHours(8).isAfter(LocalDateTime.now())) {
+ LocalDateTime nowKst = LocalDateTime.now(ZoneId.of("Asia/Seoul"));
+ if (garden.getLastWateredByOwnerAt() != null
+ && garden.getLastWateredByOwnerAt().plusHours(8).isAfter(nowKst)) {또는 garden.recordOwnerWateringTime()에서 KST로 저장하도록 변경(별도 코멘트 참고)해 두 경로를 일치시키세요.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (garden.getLastWateredByOwnerAt() != null | |
| && garden.getLastWateredByOwnerAt().plusHours(8).isAfter(LocalDateTime.now())) { | |
| throw new IllegalStateException("아직 물을 줄 수 없습니다. 8시간이 지나야 가능합니다."); | |
| } | |
| LocalDateTime nowKst = LocalDateTime.now(ZoneId.of("Asia/Seoul")); | |
| if (garden.getLastWateredByOwnerAt() != null | |
| && garden.getLastWateredByOwnerAt().plusHours(8).isAfter(nowKst)) { | |
| throw new IllegalStateException("아직 물을 줄 수 없습니다. 8시간이 지나야 가능합니다."); | |
| } |
🤖 Prompt for AI Agents
In
src/main/java/com/example/cp_main_be/domain/garden/garden/service/GardenService.java
around lines 64-67, the 8-hour cooldown comparison mixes LocalDateTime.now()
(server default TZ) with timestamps that are intended to be KST, causing
boundary mismatches; update the comparison to use the same time basis as the
rest of the app by either (A) converting both sides to Instant and comparing
Instants (preferred global approach), or (B) use
ZonedDateTime.now(ZoneId.of("Asia/Seoul")) (or
LocalDateTime.now(ZoneId.of("Asia/Seoul")) if stored values are KST) so both the
stored lastWatered time and the current time are in Asia/Seoul; alternatively,
if you choose to change storage, ensure garden.recordOwnerWateringTime() stores
KST and adjust this check to use KST consistently.
| LocalDateTime startOfWateringDay = getStartOfCurrentWateringDay(); | ||
|
|
||
| // 1. 하루에 3회 제한 체크 | ||
| int todayWateringCount = | ||
| friendWateringLogRepository.countByWaterGiverAndWateredAtAfter(actor, startOfWateringDay); | ||
| if (todayWateringCount >= MAX_FRIEND_WATERING_PER_DAY) { | ||
| throw new IllegalStateException("오늘은 다른 사람의 정원에 더 이상 물을 줄 수 없습니다. (일일 3회 제한)"); | ||
| } | ||
|
|
||
| // 2. 같은 정원에 하루 한 번 제한 체크 | ||
| boolean alreadyWatered = | ||
| friendWateringLogRepository.existsByWaterGiverAndWateredGardenAndWateredAtAfter( | ||
| actor, garden, startOfWateringDay); | ||
| if (alreadyWatered) { | ||
| throw new IllegalStateException("이 정원에는 오늘 이미 물을 주었습니다."); | ||
| } | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
일일 제한 판정의 경합 상태 및 시간대 일관성
- 동일 사용자가 거의 동시에 두 요청을 보내면,
exists/count체크 사이에 로그가 삽입되어 중복 허용 가능성이 있습니다(포인트 중복 부여 위험). startOfWateringDay는 KST 기준이지만wateredAt저장이 서버 TZ이면 경계에서 틀어질 수 있습니다(엔티티 코멘트 참고).
대응 방안:
- DB 제약(유니크) 또는 “체크+삽입”을 하나의 원자적 연산으로 만들기 위한 잠금. 예)
FriendWateringLog저장 전, 대상(actor, garden)구간에 대해 짧은 PESSIMISTIC_WRITE를 잡거나, “KST 일자 버킷” 컬럼에 유니크 인덱스를 둠. - 최소한 서비스 레벨에서 실패 시(유니크 위반 등) 예외를 캐치하고 포인트 중복 부여를 롤백하는 보완이 필요합니다.
예시(간단 보완 — 성공 후 기록/포인트 순서 조정과 기록 실패 시 롤백):
- userService.addExperience(actorId, WATERING_POINTS);
- garden.increaseWaterCount();
- FriendWateringLog log = FriendWateringLog.builder().waterGiver(actor).wateredGarden(garden).build();
- friendWateringLogRepository.save(log);
+ FriendWateringLog log = FriendWateringLog.builder().waterGiver(actor).wateredGarden(garden).build();
+ friendWateringLogRepository.save(log); // 기록 성공 보장 후
+ garden.increaseWaterCount();
+ userService.addExperience(actorId, WATERING_POINTS);가능하다면 DB 유니크 제약을 병행하는 것이 가장 확실합니다.
| @Entity | ||
| @Getter | ||
| @NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
| @EntityListeners(AuditingEntityListener.class) | ||
| @Table(name = "friend_watering_log") | ||
| public class FriendWateringLog { |
There was a problem hiding this comment.
🛠️ Refactor suggestion
조회 패턴에 맞춘 인덱스/제약 추가로 성능·정합성 보강
- 레포지토리는
countByWaterGiverAndWateredAtAfter와existsByWaterGiverAndWateredGardenAndWateredAtAfter를 자주 수행합니다. 이에 맞춘 복합 인덱스가 없으면 테이블 성장 시 성능 저하가 큽니다. - “같은 정원 하루 1회”는 경쟁 상황에서 이중 기록이 날 수 있어(포인트 중복 부여) DB 레벨 제약이 있으면 안전합니다.
인덱스 추가 예시:
-@Table(name = "friend_watering_log")
+@Table(
+ name = "friend_watering_log",
+ indexes = {
+ @Index(name = "idx_fwl_giver_at", columnList = "water_giver_id, watered_at"),
+ @Index(name = "idx_fwl_giver_garden_at", columnList = "water_giver_id, watered_garden_id, watered_at")
+ }
+)DB 제약(옵션):
- 가능하면 “KST 기준 일자 버킷” 컬럼(예:
watered_day_kst DATE)을 추가해(water_giver_id, watered_garden_id, watered_day_kst)유니크 제약을 두면 애플리케이션 레벨 경쟁 상태를 원천 차단할 수 있습니다. 표준 JPA만으로는 표현이 어렵지만, 마이그레이션 스크립트로 처리 가능합니다.
Also applies to: 25-35
🤖 Prompt for AI Agents
In
src/main/java/com/example/cp_main_be/domain/garden/wateringlog/domain/FriendWateringLog.java
around lines 14-19 (and also apply same changes for lines 25-35), the entity
lacks DB indexes and a uniqueness constraint required by hotspots: add composite
indexes to support the frequent repository queries (e.g., an index on
(water_giver_id, watered_at) and another on (water_giver_id, watered_garden_id,
watered_at)) via your migration script or JPA @Table(indexes=...) and add a
DB-level uniqueness constraint to prevent duplicate same-garden-per-day records
by introducing a new DATE column (e.g., watered_day_kst) populated by the
application or migration (KST bucket) and create a unique constraint on
(water_giver_id, watered_garden_id, watered_day_kst) in the migration; implement
the schema changes in a migration (Flyway/Liquibase) to add the column, populate
existing rows, create the indexes and the unique constraint, and update the
entity annotations if you mirror indexes/constraints in JPA.
| @CreatedDate | ||
| @Column(nullable = false, updatable = false) | ||
| private LocalDateTime wateredAt; |
There was a problem hiding this comment.
감사 타임스탬프의 시간대 일관성 확보 필요
@CreatedDate가 서버 기본 시간대를 따르는 경우, 서비스는 Asia/Seoul 기준으로 일자 경계를 계산하므로 일일 제한 판단이 경계 시간대에서 틀어질 수 있습니다.
Auditing의 DateTimeProvider를 KST로 고정하거나, 엔티티에 @PrePersist로 KST 기준 값을 강제 설정하세요(권장은 Instant 저장).
예시(전역 설정):
// @Configuration
@EnableJpaAuditing(dateTimeProviderRef = "seoulDateTimeProvider")
class JpaConfig {
@Bean
DateTimeProvider seoulDateTimeProvider() {
return () -> Optional.of(LocalDateTime.now(ZoneId.of("Asia/Seoul")));
}
}🤖 Prompt for AI Agents
In
src/main/java/com/example/cp_main_be/domain/garden/wateringlog/domain/FriendWateringLog.java
around lines 33-35, the @CreatedDate LocalDateTime field may use the server
default timezone causing inconsistent daily-boundary calculations; fix by
ensuring KST-consistent timestamps either by configuring a JPA DateTimeProvider
bean that returns LocalDateTime.now(ZoneId.of("Asia/Seoul")) (and register it
via @EnableJpaAuditing(dateTimeProviderRef = "...")) or by replacing the field
with an Instant (recommended) and/or setting the KST value explicitly in an
@PrePersist method that assigns Instant.now() or
LocalDateTime.now(ZoneId.of("Asia/Seoul")) before persist.
📝 개요
이번 PR의 핵심 내용을 한 줄로 요약해 주세요.
💻 작업 내용
이번 PR에서 작업한 내용을 상세히 설명해 주세요.
작업 내용 1
작업 내용 2
...
✅ PR 체크리스트
PR을 보내기 전에 아래 체크리스트를 확인해 주세요.
커밋 메시지는 포맷에 맞게 작성했나요?
스스로 코드를 다시 한번 검토했나요?
관련 이슈를 연결했나요?
빌드 및 테스트가 로컬에서 성공했나요?
🔗 관련 이슈
이번 PR과 관련된 이슈 번호를 기재해 주세요.
스크린샷 (선택)
UI 변경 사항이 있다면 스크린샷을 첨부해 주세요.
Summary by CodeRabbit