refactor: 프로젝트 정합성 관련 수정 - #70
Conversation
…i-alignment-refactor
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthrough프로젝트와 파일에 핀 생성·해제 API와 핀 상태 응답이 추가되었습니다. 프로젝트 목록은 핀·역할·미리보기 정보를 포함하며, 공지에는 사용자별 읽음 기록과 읽음 처리 API가 추가되었습니다. 비디오 기타 카테고리명은 고정 문자열로 변경되었습니다. Changes프로젝트 핀
프로젝트 파일 핀
프로젝트 공지 읽음 처리
비디오 카테고리 라벨
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)프로젝트 핀 처리sequenceDiagram
participant ProjectController
participant ProjectService
participant ProjectPinRepository
participant project_pin
ProjectController->>ProjectService: pinProject(projectId, currentUserId)
ProjectService->>ProjectPinRepository: findByUserIdAndProjectId(...)
ProjectService->>project_pin: save or delete ProjectPin
ProjectService-->>ProjectController: ProjectPinResponse
공지 읽음 처리sequenceDiagram
participant ProjectNoticeController
participant ProjectNoticeService
participant ProjectNoticeReadRepository
participant project_notice_read
ProjectNoticeController->>ProjectNoticeService: readProjectNotice(projectId, noticeId, currentUserId)
ProjectNoticeService->>ProjectNoticeReadRepository: findByNoticeIdAndUserId(...)
ProjectNoticeService->>project_notice_read: save or refresh ProjectNoticeRead
ProjectNoticeService-->>ProjectNoticeController: ProjectNoticeReadResponse
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
src/main/java/com/slatto/domain/project/service/ProjectService.java (1)
103-112: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift목록 항목별 조회를 배치화하세요.
현재 항목마다
countActiveMembers와getMemberPreviewImageUrls를 호출합니다. 최대 50개 목록에서 최대 100개의 추가 쿼리가 발생하므로, 멤버 수와 미리보기 URL도 프로젝트 ID 기준 일괄 조회로 바꾸는 편이 좋습니다.🤖 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/slatto/domain/project/service/ProjectService.java` around lines 103 - 112, Update the project list assembly around the stream mapping and replace per-item countActiveMembers and getMemberPreviewImageUrls calls with project-ID-based batch lookups prepared before mapping. Reuse the resulting maps by project ID when calling projectConverter.toSummary, preserving the existing values and behavior for each project.
🤖 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/slatto/domain/project/service/ProjectNoticeService.java`:
- Line 112: Update the response construction in updateProjectNotice to use the
current user’s actual read status instead of always passing false to toResponse.
Reuse the existing read-status lookup and preserve consistency with the notice
list; only clear the user’s read record if that is the established intended
behavior for updates.
- Around line 131-141: ProjectNoticeService의 읽음 기록 생성이 findByNoticeIdAndUserId 후
save로 분리되어 동시 요청에서 중복 키 충돌이 발생합니다. 해당 읽음 처리 로직을 DB upsert 또는 중복 키 발생 시 기존 레코드를
재조회·재시도하는 원자적 방식으로 변경하세요. 동일 공지와 사용자의 동시 PATCH 요청이 모두 성공하고 하나의 읽음 기록으로 통합되는 동시성
통합 테스트도 추가하세요.
In `@src/main/java/com/slatto/domain/project/service/ProjectService.java`:
- Around line 144-145: Update the project-pin creation flow in ProjectService
around projectPinRepository.findByUserIdAndProjectId so concurrent requests are
idempotent: use an atomic database upsert or insert-ignore operation, then
retrieve and return the existing or newly created ProjectPin when a unique-key
conflict occurs. Ensure duplicate requests for the same user and project do not
propagate a constraint exception.
---
Nitpick comments:
In `@src/main/java/com/slatto/domain/project/service/ProjectService.java`:
- Around line 103-112: Update the project list assembly around the stream
mapping and replace per-item countActiveMembers and getMemberPreviewImageUrls
calls with project-ID-based batch lookups prepared before mapping. Reuse the
resulting maps by project ID when calling projectConverter.toSummary, preserving
the existing values and behavior for each project.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: ea5869d9-1380-4c22-abcd-0c7ea6a8ab2c
📒 Files selected for processing (25)
src/main/java/com/slatto/domain/project/controller/ProjectController.javasrc/main/java/com/slatto/domain/project/controller/ProjectFileController.javasrc/main/java/com/slatto/domain/project/controller/ProjectNoticeController.javasrc/main/java/com/slatto/domain/project/converter/ProjectConverter.javasrc/main/java/com/slatto/domain/project/dto/ProjectDetailResponse.javasrc/main/java/com/slatto/domain/project/dto/ProjectFilePinResponse.javasrc/main/java/com/slatto/domain/project/dto/ProjectFileUpdateRequest.javasrc/main/java/com/slatto/domain/project/dto/ProjectFileUploadRequest.javasrc/main/java/com/slatto/domain/project/dto/ProjectListResponse.javasrc/main/java/com/slatto/domain/project/dto/ProjectNoticeReadResponse.javasrc/main/java/com/slatto/domain/project/dto/ProjectNoticeResponse.javasrc/main/java/com/slatto/domain/project/dto/ProjectPinResponse.javasrc/main/java/com/slatto/domain/project/entity/ProjectNoticeRead.javasrc/main/java/com/slatto/domain/project/entity/ProjectPin.javasrc/main/java/com/slatto/domain/project/repository/ProjectFileRepository.javasrc/main/java/com/slatto/domain/project/repository/ProjectMemberRepository.javasrc/main/java/com/slatto/domain/project/repository/ProjectNoticeReadRepository.javasrc/main/java/com/slatto/domain/project/repository/ProjectPinRepository.javasrc/main/java/com/slatto/domain/project/service/ProjectFileService.javasrc/main/java/com/slatto/domain/project/service/ProjectNoticeService.javasrc/main/java/com/slatto/domain/project/service/ProjectService.javasrc/main/java/com/slatto/domain/video/repository/VideoRepository.javasrc/main/java/com/slatto/domain/video/service/VideoService.javasrc/main/resources/db/migration/002-project-pin.sqlsrc/main/resources/db/migration/003-project-notice-read.sql
💤 Files with no reviewable changes (2)
- src/main/java/com/slatto/domain/project/dto/ProjectFileUpdateRequest.java
- src/main/java/com/slatto/domain/project/dto/ProjectFileUploadRequest.java
| projectNotice.update(request.getTitle(), request.getContent()); | ||
|
|
||
| return toResponse(projectNotice); | ||
| return toResponse(projectNotice, false); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
git ls-files 'src/main/java/com/slatto/domain/project/service/ProjectNoticeService.java' \
'src/main/java/com/slatto/domain/project/repository/*' \
'src/main/java/com/slatto/domain/project/*Read*' \
'src/main/java/com/slatto/domain/project/dto/*' \
'src/main/java/com/slatto/domain/project/controller/*'
echo '--- outline ProjectNoticeService ---'
ast-grep outline src/main/java/com/slatto/domain/project/service/ProjectNoticeService.java --view expanded || true
echo '--- nearby lines ---'
cat -n src/main/java/com/slatto/domain/project/service/ProjectNoticeService.java | sed -n '1,220p'
echo '--- search read state usages ---'
rg -n "isRead|findByNoticeIdAndUserId|readAt|ProjectNoticeRead|noticeIdAndUserId|toResponse\\(" src/main/java/com/slatto/domain/project -SRepository: SLAT-TO/SLAT-TO-BE
Length of output: 19168
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- ProjectNoticeService relevant methods ---'
sed -n '1,220p' src/main/java/com/slatto/domain/project/service/ProjectNoticeService.java | nl -ba | sed -n '1,220p'
echo '--- repository and entity definitions ---'
for f in \
src/main/java/com/slatto/domain/project/repository/ProjectNoticeReadRepository.java \
src/main/java/com/slatto/domain/project/entity/ProjectNoticeRead.java \
src/main/java/com/slatto/domain/project/dto/ProjectNoticeResponse.java
do
if [ -f "$f" ]; then
echo "### $f"
nl -ba "$f" | sed -n '1,220p'
fi
done
echo '--- direct search for update response path ---'
rg -n "return toResponse\\(|toResponse\\(.*isRead|isRead =" src/main/java/com/slatto/domain/project/service/ProjectNoticeService.java -n -SRepository: SLAT-TO/SLAT-TO-BE
Length of output: 242
수정 응답의 isRead를 현재 상태와 맞추세요.
updateProjectNotice()는 항상 false를 내려서, 이미 읽은 사용자가 공지를 수정하면 응답과 목록의 읽음 상태가 어긋납니다. 현재 사용자 기준 읽음 여부를 조회해 반환하거나, 수정 시 읽음 레코드를 초기화할 의도라면 함께 정리해야 합니다.
🤖 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/slatto/domain/project/service/ProjectNoticeService.java` at
line 112, Update the response construction in updateProjectNotice to use the
current user’s actual read status instead of always passing false to toResponse.
Reuse the existing read-status lookup and preserve consistency with the notice
list; only clear the user’s read record if that is the established intended
behavior for updates.
| ProjectNoticeRead projectNoticeRead = projectNoticeReadRepository.findByNoticeIdAndUserId( | ||
| noticeId, | ||
| currentUserId | ||
| ) | ||
| .map(existingRead -> { | ||
| existingRead.refreshReadAt(); | ||
| return existingRead; | ||
| }) | ||
| .orElseGet(() -> projectNoticeReadRepository.save( | ||
| ProjectNoticeRead.create(projectNotice, currentMember.getUser()) | ||
| )); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== ProjectNoticeService slice ==\n'
sed -n '110,170p' src/main/java/com/slatto/domain/project/service/ProjectNoticeService.java
printf '\n== Search for unique constraint / table definition ==\n'
rg -n "uk_project_notice_read_notice_user|project_notice_read" src/main resources . -g '!target' -g '!build' -g '!dist'
printf '\n== Search for tests / upsert / concurrency handling ==\n'
rg -n "refreshReadAt|findByNoticeIdAndUserId|ProjectNoticeRead.create|concurrent|race|duplicate|unique constraint|upsert" src/test src/main -g '!target' -g '!build' -g '!dist'Repository: SLAT-TO/SLAT-TO-BE
Length of output: 4858
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== ProjectNoticeRead entity ==\n'
sed -n '1,220p' src/main/java/com/slatto/domain/project/entity/ProjectNoticeRead.java
printf '\n== ProjectNoticeRead repository ==\n'
sed -n '1,220p' src/main/java/com/slatto/domain/project/repository/ProjectNoticeReadRepository.java
printf '\n== Transaction / isolation / duplicate-key handling search ==\n'
rg -n "`@Transactional`|Isolation|Deadlock|DuplicateKey|DataIntegrityViolation|ConstraintViolation|upsert|ON DUPLICATE|retry" src/main/java src/test src/main/resources -g '!target' -g '!build' -g '!dist'Repository: SLAT-TO/SLAT-TO-BE
Length of output: 9286
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== VideoService duplicate-key handling ==\n'
sed -n '140,190p' src/main/java/com/slatto/domain/video/service/VideoService.java
printf '\n== NotificationSettingRepository upsert pattern ==\n'
sed -n '1,120p' src/main/java/com/slatto/domain/notification/repository/NotificationSettingRepository.java
printf '\n== GlobalExceptionHandler ==\n'
sed -n '1,220p' src/main/java/com/slatto/global/exception/GlobalExceptionHandler.java
printf '\n== Tests for ProjectNoticeRead / readProjectNotice ==\n'
rg -n "ProjectNoticeRead|readProjectNotice|project_notice_read" src/test -g '!target' -g '!build' -g '!dist'Repository: SLAT-TO/SLAT-TO-BE
Length of output: 7041
동시 읽음 처리를 원자적으로 바꾸세요. src/main/java/com/slatto/domain/project/service/ProjectNoticeService.java:131-141에서 findByNoticeIdAndUserId 후 save로 분기하면 동일 사용자/공지의 동시 PATCH가 둘 다 미존재로 판단해 uk_project_notice_read_notice_user 충돌로 한 요청이 실패합니다. DB upsert 또는 중복 키 재조회/재시도 방식으로 바꾸고, 동시 요청 통합 테스트를 추가하세요.
🤖 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/slatto/domain/project/service/ProjectNoticeService.java`
around lines 131 - 141, ProjectNoticeService의 읽음 기록 생성이 findByNoticeIdAndUserId
후 save로 분리되어 동시 요청에서 중복 키 충돌이 발생합니다. 해당 읽음 처리 로직을 DB upsert 또는 중복 키 발생 시 기존 레코드를
재조회·재시도하는 원자적 방식으로 변경하세요. 동일 공지와 사용자의 동시 PATCH 요청이 모두 성공하고 하나의 읽음 기록으로 통합되는 동시성
통합 테스트도 추가하세요.
| ProjectPin projectPin = projectPinRepository.findByUserIdAndProjectId(currentUserId, projectId) | ||
| .orElseGet(() -> projectPinRepository.save(ProjectPin.create(currentMember.getUser(), project))); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
동시 고정 요청을 멱등하게 처리하세요.
조회 후 저장은 원자적이지 않습니다. 같은 사용자와 프로젝트에 대한 동시 POST 요청은 둘 다 미존재 상태를 확인한 뒤 uk_project_pin_user_project 충돌로 한 요청이 500 오류가 됩니다. DB upsert/insert-ignore 후 재조회하는 방식 등으로 기존 핀을 반환하도록 처리하세요.
🤖 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/slatto/domain/project/service/ProjectService.java` around
lines 144 - 145, Update the project-pin creation flow in ProjectService around
projectPinRepository.findByUserIdAndProjectId so concurrent requests are
idempotent: use an atomic database upsert or insert-ignore operation, then
retrieve and return the existing or newly created ProjectPin when a unique-key
conflict occurs. Ensure duplicate requests for the same user and project do not
propagate a constraint exception.
🔗 관련 이슈 (Related Issue)
📝 작업 내용
주요 검토 파일:
src/main/java/com/slatto/domain/project/controller/ProjectController.javasrc/main/java/com/slatto/domain/project/controller/ProjectFileController.javasrc/main/java/com/slatto/domain/project/controller/ProjectNoticeController.javasrc/main/java/com/slatto/domain/project/service/ProjectService.javasrc/main/java/com/slatto/domain/project/service/ProjectFileService.javasrc/main/java/com/slatto/domain/project/service/ProjectNoticeService.javasrc/main/java/com/slatto/domain/project/entity/ProjectPin.javasrc/main/java/com/slatto/domain/project/entity/ProjectNoticeRead.java1. 프로젝트 목록 카드 응답 보강
프로젝트 목록 화면에서 필요한 카드 표시용 필드를 보강했습니다.
roleNamespreviewImageUrldeadlineProgressPercentlastActivityAtmemberPreviewImageUrlsmemberCountisPinnedpinnedAtmyPermission프로젝트 수정/삭제 가능 여부는 별도 boolean 대신
myPermission으로 판단할 수 있도록 정리했습니다.2. 프로젝트 고정 API 추가
사용자별 프로젝트 고정을 처리할 수 있도록 API를 추가했습니다.
POST /api/v1/projects/{projectId}/pinDELETE /api/v1/projects/{projectId}/pin프로젝트 고정은 사용자별 상태이므로
project_pin테이블로 분리했습니다.3. 파일 고정 API 추가
파일 목록에서 고정 상태를 별도로 변경할 수 있도록 API를 추가했습니다.
POST /api/v1/projects/{projectId}/files/{fileId}/pinDELETE /api/v1/projects/{projectId}/files/{fileId}/pin파일 업로드/수정 request에서는
isPinned을 받지 않고, 고정/해제는 별도 API로 처리하도록 정리했습니다.4. 공지 읽음 상태 추가
프로젝트 공지 목록에서 사용자별 읽음 여부를 표시할 수 있도록 읽음 상태를 추가했습니다.
isRead추가PATCH /api/v1/projects/{projectId}/notices/{noticeId}/read추가project_notice_read테이블에서 관리공지사항 파일 첨부는 이번 범위에 포함하지 않았습니다.
5. API 필드 정합성 보정
프로젝트 파트에서 혼동될 수 있던 필드를 정리했습니다.
roleNames로 통일isPinned제거isPinned,isFinal유지6. 고정 항목 cursor pagination 보정
프로젝트와 파일 목록이 고정 항목 우선 정렬을 사용하므로, cursor pagination 조건도 정렬 기준에 맞게 보정했습니다.
ProjectPin.pinnedAt기준 cursor 처리ProjectFile.pinnedAt기준 cursor 처리✅ 검증
./gradlew compileJava./gradlew test💬 To Reviewers
이번 PR은 S3 파일 API 이후 화면/API 명세와 맞지 않는 부분을 보정하는 작업이였습니다!
✅ PR 체크리스트
Summary by CodeRabbit
새로운 기능
변경 사항