[Feat]#111 커뮤니티 댓글 isMine 응답 및 게시글 수정 API 추가 - #112
Conversation
- 댓글 목록 조회 응답에 isMine 추가 (GET /api/community/{communityId}/comments)
- 게시글 수정 API 추가 (POST /api/community/{communityId}, multipart)
- deleteImageIds 로 기존 이미지 부분 삭제, images 로 새 이미지 추가
- 톰캣이 multipart 본문을 POST 에서만 파싱해 PUT/PATCH 대신 POST 사용
- 첨부 이미지만 바뀐 경우에도 updatedAt 갱신
- 상세 조회 응답에 images(imageId + imageUrl) 추가, 기존 imageUrls 는 유지
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
📝 WalkthroughWalkthrough커뮤니티 게시글 수정 API가 추가되었습니다. 기존 이미지 삭제와 새 이미지 추가를 지원합니다. 상세 이미지 응답에 이미지 ID가 포함됩니다. 댓글 응답에는 Changes커뮤니티 게시글 수정 기능
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related issues
Possibly related PRs
Suggested reviewers: Sequence Diagram(s)sequenceDiagram
participant Client
participant CommunityController
participant CommunityService
participant Database
participant S3
Client->>CommunityController: 멀티파트 게시글 수정 요청
CommunityController->>CommunityService: updateCommunityPost(userId, communityId, request)
CommunityService->>Database: 게시글 및 이미지 수정
CommunityService->>S3: 삭제 이미지 객체 정리
CommunityService->>CommunityController: CommunityUpdateResponseDTO
CommunityController->>Client: 수정 성공 응답
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
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.
Pull request overview
커뮤니티 도메인에서 댓글 목록 조회 응답에 isMine 플래그를 추가하고, 게시글 수정 API(멀티파트 기반) 및 이미지 부분 수정(delete/add) 기능을 도입해 클라이언트가 수정 화면을 구성/갱신할 수 있도록 확장한 PR입니다.
Changes:
- 댓글 목록 조회 응답에
isMine추가 및 컨트롤러/서비스 시그니처 확장 - 커뮤니티 게시글 수정 API 추가 + 수정 응답 DTO 신설
- 상세 조회 응답에
images(imageId + imageUrl)추가(기존imageUrls유지) 및 이미지 편집 로직 추가
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/main/java/com/redo/domain/community/service/CommunityService.java | 댓글 isMine 계산 추가, 게시글 수정 및 이미지 부분 수정 로직 추가, 상세 이미지 응답 구조 변경 |
| src/main/java/com/redo/domain/community/exception/code/CommunitySuccessCode.java | 게시글 수정 성공 코드 추가 |
| src/main/java/com/redo/domain/community/exception/code/CommunityErrorCode.java | 게시글 이미지 NOT_FOUND 에러 코드 추가 |
| src/main/java/com/redo/domain/community/entity/Community.java | 게시글 update/touch 메서드 추가 |
| src/main/java/com/redo/domain/community/dto/res/CommunityUpdateResponseDTO.java | 게시글 수정 응답 DTO 추가 |
| src/main/java/com/redo/domain/community/dto/res/CommunityImageResponseDTO.java | 이미지 id + presigned url 응답 DTO 추가 |
| src/main/java/com/redo/domain/community/dto/res/CommunityDetailResponseDTO.java | 상세 응답에 images 필드 추가(기존 imageUrls 유지) |
| src/main/java/com/redo/domain/community/dto/res/CommunityCommentResponseDTO.java | 댓글 응답에 isMine 추가 및 직렬화 이름 고정 |
| src/main/java/com/redo/domain/community/dto/req/CommunityUpdateRequestDTO.java | 게시글 수정(삭제 이미지 id + 추가 이미지) 요청 DTO 추가 |
| src/main/java/com/redo/domain/community/converter/CommunityConverter.java | 상세/수정/댓글 응답 변환 로직 확장(images/isMine/updateResponse) |
| src/main/java/com/redo/domain/community/controller/CommunityController.java | 게시글 수정 API 추가 및 댓글 목록 조회에 userId 전달 |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| List<CommunityImg> currentImages = communityImgRepository.findByCommunityOrderByDisplayOrderAsc(community); | ||
|
|
||
| List<String> deletedImageKeys = deleteImages(currentImages, deleteImageIds); | ||
| // 남은 이미지와 순서가 겹치지 않도록 기존 display_order 최댓값 다음부터 이어서 저장한다. | ||
| List<String> addedImageKeys = saveImages(community, images, nextDisplayOrder(currentImages)); |
| comment, | ||
| getNickname(profile), | ||
| getProfileImageUrl(profile), | ||
| getCharacterCode(profile) | ||
| getCharacterCode(profile), | ||
| isMine(userId, comment) |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@src/main/java/com/redo/domain/community/dto/req/CommunityUpdateRequestDTO.java`:
- Line 13: CommunityUpdateRequestDTO의 title에 `@Size`(max = 100)을 추가하고,
CommunityService의 validatePostRequest()에서 editImages()가 실행되기 전에 동일한 최대 길이 검증을
수행하도록 수정하십시오. Community.title의 100자 제한과 일치시키고, 초과 요청은 S3 작업이나 영속화 전에 거부되도록 기존 검증
흐름을 유지하십시오.
In `@src/main/java/com/redo/domain/community/service/CommunityService.java`:
- Around line 346-356: Separate upload-failure cleanup from transaction rollback
in the image update flow around saveImages and S3Service.uploadAll. Ensure
rollbackUploadedObjects failures do not propagate or interfere with database
restoration; instead, reliably record failed object keys for retry or persist
them for asynchronous cleanup, while preserving the existing post-commit
deletion behavior.
- Around line 162-175: Apply concurrency control to updateCommunityPost and the
Community retrieval path so simultaneous edits cannot calculate duplicate image
displayOrder values from the same state. Add optimistic versioning with a
conflict response, or use a pessimistic write lock for this update flow; ensure
the selected mechanism covers editImages and maps conflicts to the established
error handling.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 13d55ae3-1dcc-48af-96a3-9e590281965a
📒 Files selected for processing (11)
src/main/java/com/redo/domain/community/controller/CommunityController.javasrc/main/java/com/redo/domain/community/converter/CommunityConverter.javasrc/main/java/com/redo/domain/community/dto/req/CommunityUpdateRequestDTO.javasrc/main/java/com/redo/domain/community/dto/res/CommunityCommentResponseDTO.javasrc/main/java/com/redo/domain/community/dto/res/CommunityDetailResponseDTO.javasrc/main/java/com/redo/domain/community/dto/res/CommunityImageResponseDTO.javasrc/main/java/com/redo/domain/community/dto/res/CommunityUpdateResponseDTO.javasrc/main/java/com/redo/domain/community/entity/Community.javasrc/main/java/com/redo/domain/community/exception/code/CommunityErrorCode.javasrc/main/java/com/redo/domain/community/exception/code/CommunitySuccessCode.javasrc/main/java/com/redo/domain/community/service/CommunityService.java
| // (둘 다 보내지 않으면 기존 이미지는 그대로 유지된다.) | ||
| public record CommunityUpdateRequestDTO( | ||
| @NotNull Integer category, | ||
| @NotBlank String title, |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Locate relevant files"
fd -a 'CommunityUpdateRequestDTO.java|Community.java|CommunityService.java' src/main/java || true
echo
echo "Files with validation annotations around community DTOs"
rg -n "class CommunityUpdateRequest|record CommunityUpdateRequest|`@NotBlank`|`@Size`|CommunityUpdateRequestDTO" src/main/java || true
echo
echo "CommunityUpdateRequestDTO.java"
cat -n src/main/java/com/redo/domain/community/dto/req/CommunityUpdateRequestDTO.java
echo
echo "Community.java relevant fields"
sed -n '1,90p' src/main/java/com/redo/domain/community/entity/Community.java | cat -n
echo
echo "CommunityService.java relevant update/image lines"
sed -n '260,350p' src/main/java/com/redo/domain/community/service/CommunityService.java | cat -n
echo
echo "CommunityService.java validation methods containing CommunityUpdateRequest"
rg -n "CommunityUpdateRequest|`@NotBlank`|`@Size`|editImages|updateCommunity|save|saveAndFlush|validation" src/main/java/com/redo/domain/community/service src/main/java/com/redo/domain/community -g '*.java' || trueRepository: REDO-Team/Back
Length of output: 19062
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "CommunityService.java updateCommunityPost and validation helper"
sed -n '70,190p' src/main/java/com/redo/domain/community/service/CommunityService.java | cat -n
echo
echo "CommunityService.java editImages and deleteImages"
sed -n '334,390p' src/main/java/com/redo/domain/community/service/CommunityService.java | cat -n
echo
echo "CommunityController.java updateCommunityPost annotations"
sed -n '95,120p' src/main/java/com/redo/domain/community/controller/CommunityController.java | cat -n
echo
echo "CommunityException and CODE definitions related to title/content"
rg -n "TITLE_REQUIRED|CONTENT_REQUIRED|COMMUNITY_NOT_FOUND|GeneralExceptionAdvice|ErrorCode|`@RestControllerAdvice`|ExceptionHandler" src/main/java -g '*.java' || trueRepository: REDO-Team/Back
Length of output: 50370
수정 API에서 title 길이를 영속화 전에 제한하십시오.
Community.title은 length = 100으로 제한되지만, CommunityUpdateRequestDTO.title은 공백만 검증합니다. 101자 제목 요청은 editImages()의 S3 업로드/삭제 작업 이후 flush에서 실패할 수 있습니다. DTO에 @Size(max = 100)을 적용하고, editImages() 전 CommunityService.validatePostRequest()에도 같은 길이 검증을 추가하십시오.
수정 예시
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
- `@NotBlank` String title,
+ `@NotBlank` `@Size`(max = 100) String title,🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@src/main/java/com/redo/domain/community/dto/req/CommunityUpdateRequestDTO.java`
at line 13, CommunityUpdateRequestDTO의 title에 `@Size`(max = 100)을 추가하고,
CommunityService의 validatePostRequest()에서 editImages()가 실행되기 전에 동일한 최대 길이 검증을
수행하도록 수정하십시오. Community.title의 100자 제한과 일치시키고, 초과 요청은 S3 작업이나 영속화 전에 거부되도록 기존 검증
흐름을 유지하십시오.
| Community community = getActiveCommunity(communityId); | ||
| if (!community.getUser().getId().equals(userId)) { | ||
| throw new CommunityException(CommunityErrorCode.NOT_POST_OWNER); | ||
| } | ||
|
|
||
| community.update(toCategory(request.category()), request.title(), request.content()); | ||
|
|
||
| // 제목/본문 변경 없이 첨부 이미지만 바뀌면 게시글 컬럼이 그대로라 UPDATE 가 나가지 않으므로 수정 시각을 직접 갱신한다. | ||
| if (editImages(community, request.deleteImageIds(), request.images())) { | ||
| community.touch(); | ||
| } | ||
|
|
||
| // 응답에 갱신된 updatedAt(@PreUpdate 로 채워진다)을 담기 위해 변경 내용을 먼저 반영한다. | ||
| communityRepository.flush(); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 빌드나 테스트를 실행하지 않고 잠금 구성을 확인한다.
ast-grep outline src/main/java/com/redo/domain/community/entity/Community.java --items all
rg -n -C 4 '`@Version`|`@Lock`|LockModeType|PESSIMISTIC|OPTIMISTIC|findByIdAndDeletedAtIsNull' \
src/main/java/com/redo/domain/community/entity/Community.java \
src/main/java/com/redo/domain/community/repository/CommunityRepository.javaRepository: REDO-Team/Back
Length of output: 2140
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== relevant Community methods =="
sed -n '1,120p' src/main/java/com/redo/domain/community/entity/Community.java
sed -n '130,210p' src/main/java/com/redo/domain/community/entity/Community.java
sed -n '320,410p' src/main/java/com/redo/domain/community/service/CommunityService.java
echo "== repository methods =="
sed -n '1,80p' src/main/java/com/redo/domain/community/repository/CommunityRepository.java
echo "== transaction annotations for update methods =="
rg -n -C 4 '`@Transactional`|editImages|findByIdAndDeletedAtIsNull\(' \
src/main/java/com/redo/domain/community/service/CommunityService.java \
src/main/java/com/redo/domain/community/repository/CommunityRepository.javaRepository: REDO-Team/Back
Length of output: 23129
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== getActiveCommunity declarations/usages =="
rg -n -C 3 'getActiveCommunity\(Long communityId\)|getActiveCommunity\(' src/main/java/com/redo/domain/community/service/CommunityService.java
echo "== CommunityImg entity/repository relevant lock/version annotations =="
rg -n -C 3 '`@Entity`|`@Id`|`@Version`|`@Lock`|LockModeType|PESSIMISTIC|OPTIMISTIC|findByCommunityOrderByDisplayOrderAsc|deleteAll' \
src/main/java/com/redo/domain/community/entity src/main/java/com/redo/domain/community/repositoryRepository: REDO-Team/Back
Length of output: 8131
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
paths = [
Path("src/main/java/com/redo/domain/community/entity/Community.java"),
Path("src/main/java/com/redo/domain/community/repository/CommunityRepository.java"),
Path("src/main/java/com/redo/domain/community/service/CommunityService.java"),
Path("src/main/java/com/redo/domain/community/repository/CommunityImgRepository.java"),
Path("src/main/java/com/redo/domain/community/entity/CommunityImg.java"),
]
terms = ["`@Version`", "`@Lock`", "LockModeType", "PESSIMISTIC", "OPTIMISTIC", "`@Entity`", "`@Id`", "findByCommunityOrderByDisplayOrderAsc", "deleteAll"]
for path in paths:
print(f"\n== {path} ==")
text = path.read_text()
relevant = any(term in text for term in terms)
print(f"contains lock/version/entity-query terms: {relevant}")
if relevant:
for lineno, line in enumerate(text.splitlines(), 1):
if any(term in line for term in terms):
print(f"{lineno}: {line}")
PYRepository: REDO-Team/Back
Length of output: 1244
동시 수정의 충돌 제어를 적용하십시오.
Community는 낙관적 버전을 두고 있지 않고, findByIdAndDeletedAtIsNull도 잠금을 하지 않습니다. 이 때 updateCommunityPost가 실행되면 같은 게시글의 두 요청이 같은 상태를 읽을 수 있고, editImages()는 같은 이미지 목록에서 nextDisplayOrder를 계산해 새 이미지에 중복된 displayOrder를 지정할 수 있습니다. 수정 대상에 낙관적 잠금과 충돌 응답, 또는 이 경로의 비관적 쓰기 잠금을 적용하십시오.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/main/java/com/redo/domain/community/service/CommunityService.java` around
lines 162 - 175, Apply concurrency control to updateCommunityPost and the
Community retrieval path so simultaneous edits cannot calculate duplicate image
displayOrder values from the same state. Add optimistic versioning with a
conflict response, or use a pessimistic write lock for this update flow; ensure
the selected mechanism covers editImages and maps conflicts to the established
error handling.
Source: Coding guidelines
| List<String> deletedImageKeys = deleteImages(currentImages, deleteImageIds); | ||
| // 남은 이미지와 순서가 겹치지 않도록 기존 display_order 최댓값 다음부터 이어서 저장한다. | ||
| List<String> addedImageKeys = saveImages(community, images, nextDisplayOrder(currentImages)); | ||
|
|
||
| // S3 객체 정리는 DB 반영 이후에 수행하고, 실패하더라도 수정 자체는 성공으로 처리한다(고아 객체는 로그로 남긴다). | ||
| try { | ||
| s3Service.deleteAll(deletedImageKeys); | ||
| } catch (S3Exception exception) { | ||
| log.error("Failed to delete removed community images from S3. communityId={}, keys={}", | ||
| community.getId(), deletedImageKeys, exception); | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Files:"
fd -a 'CommunityService\.java|S3Service\.java' . | sed 's#^\./##'
echo
echo "CommunityService outline:"
FILE=$(fd 'CommunityService\.java' . | head -n1)
if [ -n "${FILE:-}" ]; then
wc -l "$FILE"
ast-grep outline "$FILE" || true
echo
echo "Relevant CommunityService sections:"
sed -n '300,410p' "$FILE" | cat -n -v
fi
echo
echo "S3Service outline:"
S3FILE=$(fd 'S3Service\.java' . | head -n1)
if [ -n "${S3FILE:-}" ]; then
wc -l "$S3FILE"
ast-grep outline "$S3FILE" || true
echo
echo "Relevant S3Service sections:"
sed -n '1,170p' "$S3FILE" | cat -n -v
fi
echo
echo "Search transactional and publish after commit patterns:"
rg -n '`@Transactional`|AfterCommit|TransactionSynchronization|TransactionSynchronizationManager|AfterTransaction|PostTransaction|commit|deleteAll|saveImages|editImages|flush' src/main/java || trueRepository: REDO-Team/Back
Length of output: 26424
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "CommunityService transactional and update sections:"
FILE=$(fd 'CommunityService\.java' . | head -n1)
sed -n '130,285p' "$FILE" | cat -n -v
echo
echo "S3Service remaining rollback section:"
S3FILE=$(fd 'S3Service\.java' . | head -n1)
sed -n '180,240p' "$S3FILE" | cat -n -v
echo
echo "Search for after-commit/transaction-synchronization implementation:"
rg -n 'TransactionSynchronization|AfterCommit|afterCommit|commitFailed|prepareCommit|TransactionSynchronizationRegistry|ApplicationEventPublisher|`@TransactionalEventListener`|EventPublisher|TransactionStatus|afterRollback' src/main/java || trueRepository: REDO-Team/Back
Length of output: 13098
업로드 실패 처리를 롤백 전 고아 객체 제거와 분리하십시오.
S3Service.uploadAll()은 rollbackUploadedObjects()가 삭제 실패를 로그만 남긴 뒤 throw하므로, 업로드 중 실패하면 임시 S3 객체가 삭제되지 않고 보존될 수 있습니다. 트랜잭션 롤백 시 S3 실패가 DB 복원을 방해하지 않도록 별도 보상/정리 경로로 분리하고, 실패한 키의 재시도 또는 영속화된 후처리 기록을 저장하십시오.
🧰 Tools
🪛 PMD (7.26.0)
[Low] 354-355: InvalidLogMessageFormat (Error Prone): Too many arguments, expected 2 arguments but found 3
(InvalidLogMessageFormat (Error Prone))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/main/java/com/redo/domain/community/service/CommunityService.java` around
lines 346 - 356, Separate upload-failure cleanup from transaction rollback in
the image update flow around saveImages and S3Service.uploadAll. Ensure
rollbackUploadedObjects failures do not propagate or interfere with database
restoration; instead, reliably record failed object keys for retry or persist
them for asynchronous cleanup, while preserving the existing post-commit
deletion behavior.
Source: Coding guidelines
#️⃣연관된 이슈
📝작업 내용
스크린샷 (선택)
💬리뷰 요구사항(선택)
PR 전 필수 체크리스트
테스트 결과
Summary by CodeRabbit
새 기능
개선 사항