fix: 프로젝트 카드 활동 시각 및 프로필 이미지 업로드 지원 - #125
Conversation
|
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프로젝트 목록이 활동 로그의 최신 시각을 사용하도록 변경되었습니다. 프로필 이미지 멀티파트 업로드와 스토리지 정리 처리가 추가되었습니다. Flyway 설정과 V001~V010 데이터베이스 마이그레이션이 추가되었습니다. Changes프로젝트 최신 활동 시각
Flyway 스키마 마이그레이션
프로필 이미지 업로드
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant UserController
participant UserService
participant StorageService
participant Users
Client->>UserController: 멀티파트 프로필 이미지 업로드
UserController->>UserService: uploadProfileImage(userId, file)
UserService->>StorageService: 이미지 객체 업로드
UserService->>Users: 프로필 이미지 URL 갱신
UserService-->>UserController: UserProfileImageResponse
UserController-->>Client: 성공 응답
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 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.
Actionable comments posted: 5
🧹 Nitpick comments (2)
src/test/resources/application.yml (1)
19-20: 🗄️ Data Integrity & Integration | 🔵 TrivialMySQL Flyway 마이그레이션 검증 단계를 별도로 추가하세요.
Line 19의
spring.flyway.enabled: false와 H2ddl-auto: create-drop설정은 V001~V010을 실행하지 않습니다. 따라서 V008의 중복 데이터 처리, V009의 레거시 enum 값 처리, V010의 MySQL SQL과 데이터 이전 문제를 현재 테스트가 검증하지 못합니다.H2 테스트는 유지하되, CI 또는 배포 단계에서 Testcontainers 같은 임시 MySQL 환경으로 Flyway 마이그레이션을 실행하세요.
🤖 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/test/resources/application.yml` around lines 19 - 20, 추가 테스트 구성을 마련해 MySQL Testcontainers 환경에서 Flyway 마이그레이션을 활성화한 채 V001~V010을 실행하고 검증하세요. 기존 H2 테스트와 spring.flyway.enabled: false 설정은 유지하되, CI 또는 배포 단계에서 V008 중복 데이터 처리, V009 레거시 enum 값 처리, V010 MySQL SQL 및 데이터 이전을 확인하도록 구성하세요.src/main/resources/db/migration/V002__project_pin.sql (1)
12-13: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win복합 유니크 키의 선두 컬럼 인덱스를 중복 생성하지 마세요.
두 복합 유니크 키는 각각 첫 번째 컬럼의 단독 조건을 지원합니다. 전체 코드에서 단독 컬럼 조회가 없는지 확인한 뒤 별도 인덱스를 제거하세요.
src/main/resources/db/migration/V002__project_pin.sql#L12-L13:idx_project_pin_user_id를 제거하세요.src/main/resources/db/migration/V003__project_notice_read.sql#L12-L14:idx_project_notice_read_notice_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/resources/db/migration/V002__project_pin.sql` around lines 12 - 13, Remove the redundant standalone indexes supporting the leading columns of the composite unique keys after confirming no standalone-column queries require them: remove idx_project_pin_user_id in src/main/resources/db/migration/V002__project_pin.sql (lines 12-13) and idx_project_notice_read_notice_id in src/main/resources/db/migration/V003__project_notice_read.sql (lines 12-14).
🤖 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/user/service/UserService.java`:
- Around line 47-53: 프로필 이미지의 multipart 허용 크기를 MAX_PROFILE_IMAGE_SIZE인 10 MiB와
일치시키십시오. spring.servlet.multipart의 max-file-size와 max-request-size를 10 MiB 기준으로
조정하거나, 해당 설정을 유지해야 한다면 업로드 요청에서 validateProfileImage 이전에 파일 크기를 직접 거절하도록 변경하십시오.
- Around line 222-223: Update the upload flow in UserService so
storageService.upload is wrapped in try/catch; if it throws after creating the
object, call deleteStorageObjectQuietly(storageKey, ...) before rethrowing the
original exception, while preserving
registerUploadedFileCleanupOnRollback(storageKey) for successful uploads.
In
`@src/main/resources/db/migration/V008__recruitment_application_active_unique.sql`:
- Around line 6-11: Before creating uq_recruitment_application_active in V008,
detect existing duplicate active applications using recruitment_id and user_id,
then resolve or backfill them according to the business rules so each active
pair is unique. Run the unique-key creation only after the data cleanup has
completed.
In `@src/main/resources/db/migration/V009__activity_log_type.sql`:
- Around line 4-5: Update V009__activity_log_type.sql to migrate existing
activity_log.type values before altering the column: inspect distinct values,
map legacy RoleName entries such as DIRECTOR, PM, and EDITOR to valid
ActivityLogType strings, and explicitly handle any unmappable rows so every
persisted value is compatible with ActivityLog.type’s ActivityLogType enum
before applying the VARCHAR(50) NOT NULL change.
In `@src/main/resources/db/migration/V010__project_activity_read.sql`:
- Around line 2-5: Update migration V010 so project_activity_read is created
before removing project_member.last_activity_read_at, then backfill one read
record per member from the legacy timestamp for all project activity at or
before that time. Execute the backfill before the DROP COLUMN statement,
preserving existing read status, and retain the current cleanup of the legacy
column as the final step.
---
Nitpick comments:
In `@src/main/resources/db/migration/V002__project_pin.sql`:
- Around line 12-13: Remove the redundant standalone indexes supporting the
leading columns of the composite unique keys after confirming no
standalone-column queries require them: remove idx_project_pin_user_id in
src/main/resources/db/migration/V002__project_pin.sql (lines 12-13) and
idx_project_notice_read_notice_id in
src/main/resources/db/migration/V003__project_notice_read.sql (lines 12-14).
In `@src/test/resources/application.yml`:
- Around line 19-20: 추가 테스트 구성을 마련해 MySQL Testcontainers 환경에서 Flyway 마이그레이션을
활성화한 채 V001~V010을 실행하고 검증하세요. 기존 H2 테스트와 spring.flyway.enabled: false 설정은 유지하되,
CI 또는 배포 단계에서 V008 중복 데이터 처리, V009 레거시 enum 값 처리, V010 MySQL SQL 및 데이터 이전을 확인하도록
구성하세요.
🪄 Autofix
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: fc3b0c39-4fa3-4980-87d9-a8652de17a87
📒 Files selected for processing (26)
.env.examplebuild.gradlesrc/main/java/com/slatto/domain/notification/repository/ActivityLogRepository.javasrc/main/java/com/slatto/domain/notification/repository/ProjectLatestActivityProjection.javasrc/main/java/com/slatto/domain/project/service/ProjectService.javasrc/main/java/com/slatto/domain/user/controller/UserController.javasrc/main/java/com/slatto/domain/user/dto/UserProfileImageResponse.javasrc/main/java/com/slatto/domain/user/entity/Users.javasrc/main/java/com/slatto/domain/user/exception/UserErrorCode.javasrc/main/java/com/slatto/domain/user/service/UserService.javasrc/main/resources/application.ymlsrc/main/resources/db/migration/005-project-member-activity-read.sqlsrc/main/resources/db/migration/V001__auth_google_login.sqlsrc/main/resources/db/migration/V002__project_pin.sqlsrc/main/resources/db/migration/V003__project_notice_read.sqlsrc/main/resources/db/migration/V004__notification_group_count.sqlsrc/main/resources/db/migration/V005__recruitment_crud.sqlsrc/main/resources/db/migration/V006__recruitment_application_status.sqlsrc/main/resources/db/migration/V007__notification_project_nullable.sqlsrc/main/resources/db/migration/V008__recruitment_application_active_unique.sqlsrc/main/resources/db/migration/V009__activity_log_type.sqlsrc/main/resources/db/migration/V010__project_activity_read.sqlsrc/test/java/com/slatto/domain/notification/repository/ActivityLogRepositoryIntegrationTest.javasrc/test/java/com/slatto/domain/user/service/PortfolioUpdatedAtTest.javasrc/test/java/com/slatto/domain/user/service/UserProfileImageServiceTest.javasrc/test/resources/application.yml
💤 Files with no reviewable changes (1)
- src/main/resources/db/migration/005-project-member-activity-read.sql
| private static final long MAX_PROFILE_IMAGE_SIZE = 10L * 1024 * 1024; | ||
| private static final String PROFILE_IMAGE_STORAGE_KEY_FORMAT = "users/%d/profile-images/%s.%s"; | ||
| private static final Map<String, Set<String>> ALLOWED_EXTENSIONS_BY_CONTENT_TYPE = Map.of( | ||
| "image/jpeg", Set.of("jpg", "jpeg"), | ||
| "image/png", Set.of("png"), | ||
| "image/webp", Set.of("webp") | ||
| ); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C 3 'multipart|max-file-size|max-request-size' \
src/main/resources/application.yml src/test/resources/application.yml
rg -n -C 3 'PROFILE_IMAGE_SIZE_EXCEEDED|10 \* 1024 \* 1024|uploadProfileImage' \
src/test/javaRepository: SLAT-TO/SLATE-TO-BE
Length of output: 2886
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Service outline =="
ast-grep outline src/main/java/com/slatto/domain/user/service/UserService.java --match UserProfileImageService --view expanded || true
echo "== Relevant service lines =="
sed -n '1,120p' src/main/java/com/slatto/domain/user/service/UserService.java | cat -n
echo "== Relevant test file outline and lines =="
ast-grep outline src/test/java/com/slatto/domain/user/service/UserProfileImageServiceTest.java --view expanded || true
sed -n '1,140p' src/test/java/com/slatto/domain/user/service/UserProfileImageServiceTest.java | cat -n
echo "== Search size boundary tests =="
rg -n -C 3 'size|Size|contentLength|1024|10 .* 1024|10MB|10MiB|PROFILE_IMAGE_SIZE_EXPIRED|PROFILE_IMAGE_SIZE_EXCEEDED|uploadProfileImage' src/test-java src/test/java || true
echo "== Search multipart config files =="
git ls-files | rg '(^|/)(application.*\.ya?ml|application.*\.properties|web.*\.ya?ml|web.*\.properties)$' | while read -r f; do
echo "--- $f"
rg -n -C 2 'multipart|max-file-size|max-request-size' "$f" || true
doneRepository: SLAT-TO/SLATE-TO-BE
Length of output: 19985
프로필 이미지 10 MiB 한도와 multipart 허용 범위를 일치시키십시오.
spring.servlet.multipart.max-file-size: 100MB, max-request-size: 105MB는 MAX_PROFILE_IMAGE_SIZE = 10L * 1024 * 1024보다 큽니다. validateProfileImage가 Spring multipart 파싱 이후에 실행되므로, 10 MiB 초과 파일이 이 체크까지 도달합니다. 허용 범위를 10 MiB 경계에 맞추거나, multipart 설정은 그대로 두고 업로드 요청에서 파일 크기를 직접 거절하도록 조정하십시오.
🤖 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/user/service/UserService.java` around lines
47 - 53, 프로필 이미지의 multipart 허용 크기를 MAX_PROFILE_IMAGE_SIZE인 10 MiB와 일치시키십시오.
spring.servlet.multipart의 max-file-size와 max-request-size를 10 MiB 기준으로 조정하거나, 해당
설정을 유지해야 한다면 업로드 요청에서 validateProfileImage 이전에 파일 크기를 직접 거절하도록 변경하십시오.
| storageService.upload(file, storageKey); | ||
| registerUploadedFileCleanupOnRollback(storageKey); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate UserService"
fd -a 'UserService.java$' . || true
echo "== outline UserService"
FILE="$(fd 'UserService.java$' . | head -n1 || true)"
if [ -n "${FILE:-}" ]; then
echo "FILE=$FILE"
wc -l "$FILE"
ast-grep outline "$FILE" --view expanded | sed -n '1,220p' || true
echo "== relevant snippets"
sed -n '180,250p' "$FILE" | nl -ba -v180
echo "== storage upload usages"
rg -n "storageService\.upload|registerUploadedFileCleanupOnRollback|upload\(" "$FILE"
fiRepository: SLAT-TO/SLATE-TO-BE
Length of output: 1486
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="./src/main/java/com/slatto/domain/user/service/UserService.java"
echo "== lines 200-235"
awk 'NR>=200 && NR<=235 { printf "%6d %s\n", NR, $0 }' "$FILE"
echo "== lines 303-350"
awk 'NR>=303 && NR<=350 { printf "%6d %s\n", NR, $0 }' "$FILE"
echo "== storageService definition/usages"
rg -n "storageService|registerUploadedFileCleanupOnRollback|registerPreviousFileDeletionAfterCommit|deleteStorageObjectQuietly|`@Transactional`" "$FILE"Repository: SLAT-TO/SLATE-TO-BE
Length of output: 5043
업로드 실패 후에도 새 객체를 삭제하십시오.
storageService.upload(file, storageKey)가 S3 저장 후 예외를 던지면 registerUploadedFileCleanupOnRollback(storageKey)가 호출되지 않아 롤백에서 새 이미지가 삭제되지 않습니다. 해당 호출을 try/catch로 감싸 예외 상황에서 deleteStorageObjectQuietly(storageKey, ...)를 먼저 수행하고 다시 예외를 던지십시오.
🤖 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/user/service/UserService.java` around lines
222 - 223, Update the upload flow in UserService so storageService.upload is
wrapped in try/catch; if it throws after creating the object, call
deleteStorageObjectQuietly(storageKey, ...) before rethrowing the original
exception, while preserving registerUploadedFileCleanupOnRollback(storageKey)
for successful uploads.
| ALTER TABLE activity_log | ||
| MODIFY COLUMN type VARCHAR(50) NOT NULL; |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== files =="
git ls-files | rg '(^|/)V009__activity_log_type\.sql$|ActivityLog|ActivityLogType|activity_log'
echo
echo "== V009 content =="
if [ -f src/main/resources/db/migration/V009__activity_log_type.sql ]; then
cat -n src/main/resources/db/migration/V009__activity_log_type.sql
fi
echo
echo "== ActivityLog references =="
rg -n "class ActivityLog|ActivityLog|`@Enumerated`|EnumType\.STRING|activity_log|class ActivityLogType|enum ActivityLogType|enum RoleName|RoleName" -S .
echo
echo "== migration metadata around activity_log =="
fd '.*activity.*\.sql$|.*migration.*\.sql$' src/main/resources/db/migration 2>/dev/null | sort | xargs -r -n1 sh -c 'echo "--- $0 ---"; sed -n "1,160p" "$0"'Repository: SLAT-TO/SLATE-TO-BE
Length of output: 50375
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== ActivityLog entity =="
cat -n src/main/java/com/slatto/domain/notification/entity/ActivityLog.java
echo
echo "== ActivityLogType enum =="
cat -n src/main/java/com/slatto/domain/notification/enums/ActivityLogType.java
echo
echo "== RoleName enum =="
cat -n src/main/java/com/slatto/domain/user/enums/RoleName.java
echo
echo "== earlier migrations containing activity_log/type =="
rg -n "activity_log|CREATE TABLE|ALTER TABLE.*type|type" src/main/resources/db/migration -g '*.sql' --max-count 200
echo
echo "== ActivityLogRepository methods =="
cat -n src/main/java/com/slatto/domain/notification/repository/ActivityLogRepository.javaRepository: SLAT-TO/SLATE-TO-BE
Length of output: 8861
레거시 activity_log.type 값을 ActivityLogType 값으로 변환하세요.
V009__activity_log_type.sql은 기존 컬럼 값을 VARCHAR(50) 문자열로 바꾸지만 내용 변환을 하지 않습니다. ActivityLog.type은 @Enumerated(EnumType.STRING) ActivityLogType으로 매핑되고, RoleName 값(DIRECTOR, PM, EDITOR 등)은 ActivityLogType에 없습니다. 이 컬럼 데이터를 읽을 때 해당 enum 값이 없어서 엔티티 로드/마이그레이션 실행 실패가 발생할 수 있습니다.
ALTER 전에 SELECT type, COUNT(*) FROM activity_log GROUP BY type로 기존 값을 확인한 뒤, ActivityLogType 값으로 매핑하거나 호환 불가 행을 별도 처리하는 마이그레이션 SQL을 추가하세요.
🤖 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/resources/db/migration/V009__activity_log_type.sql` around lines 4 -
5, Update V009__activity_log_type.sql to migrate existing activity_log.type
values before altering the column: inspect distinct values, map legacy RoleName
entries such as DIRECTOR, PM, and EDITOR to valid ActivityLogType strings, and
explicitly handle any unmappable rows so every persisted value is compatible
with ActivityLog.type’s ActivityLogType enum before applying the VARCHAR(50) NOT
NULL change.
| -- 이전 확인 시각 방식이 일부 DB에 반영된 경우를 함께 정리한다. | ||
|
|
||
| ALTER TABLE project_member | ||
| DROP COLUMN IF EXISTS last_activity_read_at; |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== migration file =="
sed -n '1,120p' src/main/resources/db/migration/V010__project_activity_read.sql 2>/dev/null || true
echo
echo "== related project_member/last_activity_read_at/project_activity_read definitions =="
rg -n "last_activity_read_at|project_activity_read|activity_log|project_member" src/main/resources/db/migration . --glob '!**/.git/**' --glob '!**/build/**' --glob '!**/target/**' | head -200
echo
echo "== git diff stat/summary =="
git diff --stat || true
git diff --name-only || trueRepository: SLAT-TO/SLATE-TO-BE
Length of output: 11362
기존 last_activity_read_at을 백필한 뒤 컬럼을 삭제하세요.
project_member.last_activity_read_at을 드롭 후 project_activity_read를 생성하는 이 마이그레이션은 기존 읽음 시각을 새 읽음 로그로 이전하지 않습니다. 해당 값이 있으면 읽음 상태가 영구히 사라지고, 기존 로그가 ‘읽지 않은’ 활동처럼 처리될 수 있습니다. 먼저 project_activity_read를 생성해 기존 시각 이하의 활동 로그를 멤버별로 백필한 후, 레거시 컬럼을 마지막에 삭제하세요.
🤖 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/resources/db/migration/V010__project_activity_read.sql` around lines
2 - 5, Update migration V010 so project_activity_read is created before removing
project_member.last_activity_read_at, then backfill one read record per member
from the legacy timestamp for all project activity at or before that time.
Execute the backfill before the DROP COLUMN statement, preserving existing read
status, and retain the current cleanup of the legacy column as the final step.
There was a problem hiding this comment.
Caution
Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
Actionable comments posted: 5
🧹 Nitpick comments (2)
src/test/resources/application.yml (1)
19-20: 🗄️ Data Integrity & Integration | 🔵 TrivialMySQL Flyway 마이그레이션 검증 단계를 별도로 추가하세요.
Line 19의
spring.flyway.enabled: false와 H2ddl-auto: create-drop설정은 V001~V010을 실행하지 않습니다. 따라서 V008의 중복 데이터 처리, V009의 레거시 enum 값 처리, V010의 MySQL SQL과 데이터 이전 문제를 현재 테스트가 검증하지 못합니다.H2 테스트는 유지하되, CI 또는 배포 단계에서 Testcontainers 같은 임시 MySQL 환경으로 Flyway 마이그레이션을 실행하세요.
🤖 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/test/resources/application.yml` around lines 19 - 20, 추가 테스트 구성을 마련해 MySQL Testcontainers 환경에서 Flyway 마이그레이션을 활성화한 채 V001~V010을 실행하고 검증하세요. 기존 H2 테스트와 spring.flyway.enabled: false 설정은 유지하되, CI 또는 배포 단계에서 V008 중복 데이터 처리, V009 레거시 enum 값 처리, V010 MySQL SQL 및 데이터 이전을 확인하도록 구성하세요.src/main/resources/db/migration/V002__project_pin.sql (1)
12-13: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win복합 유니크 키의 선두 컬럼 인덱스를 중복 생성하지 마세요.
두 복합 유니크 키는 각각 첫 번째 컬럼의 단독 조건을 지원합니다. 전체 코드에서 단독 컬럼 조회가 없는지 확인한 뒤 별도 인덱스를 제거하세요.
src/main/resources/db/migration/V002__project_pin.sql#L12-L13:idx_project_pin_user_id를 제거하세요.src/main/resources/db/migration/V003__project_notice_read.sql#L12-L14:idx_project_notice_read_notice_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/resources/db/migration/V002__project_pin.sql` around lines 12 - 13, Remove the redundant standalone indexes supporting the leading columns of the composite unique keys after confirming no standalone-column queries require them: remove idx_project_pin_user_id in src/main/resources/db/migration/V002__project_pin.sql (lines 12-13) and idx_project_notice_read_notice_id in src/main/resources/db/migration/V003__project_notice_read.sql (lines 12-14).
🤖 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/user/service/UserService.java`:
- Around line 47-53: 프로필 이미지의 multipart 허용 크기를 MAX_PROFILE_IMAGE_SIZE인 10 MiB와
일치시키십시오. spring.servlet.multipart의 max-file-size와 max-request-size를 10 MiB 기준으로
조정하거나, 해당 설정을 유지해야 한다면 업로드 요청에서 validateProfileImage 이전에 파일 크기를 직접 거절하도록 변경하십시오.
- Around line 222-223: Update the upload flow in UserService so
storageService.upload is wrapped in try/catch; if it throws after creating the
object, call deleteStorageObjectQuietly(storageKey, ...) before rethrowing the
original exception, while preserving
registerUploadedFileCleanupOnRollback(storageKey) for successful uploads.
In
`@src/main/resources/db/migration/V008__recruitment_application_active_unique.sql`:
- Around line 6-11: Before creating uq_recruitment_application_active in V008,
detect existing duplicate active applications using recruitment_id and user_id,
then resolve or backfill them according to the business rules so each active
pair is unique. Run the unique-key creation only after the data cleanup has
completed.
In `@src/main/resources/db/migration/V009__activity_log_type.sql`:
- Around line 4-5: Update V009__activity_log_type.sql to migrate existing
activity_log.type values before altering the column: inspect distinct values,
map legacy RoleName entries such as DIRECTOR, PM, and EDITOR to valid
ActivityLogType strings, and explicitly handle any unmappable rows so every
persisted value is compatible with ActivityLog.type’s ActivityLogType enum
before applying the VARCHAR(50) NOT NULL change.
In `@src/main/resources/db/migration/V010__project_activity_read.sql`:
- Around line 2-5: Update migration V010 so project_activity_read is created
before removing project_member.last_activity_read_at, then backfill one read
record per member from the legacy timestamp for all project activity at or
before that time. Execute the backfill before the DROP COLUMN statement,
preserving existing read status, and retain the current cleanup of the legacy
column as the final step.
---
Nitpick comments:
In `@src/main/resources/db/migration/V002__project_pin.sql`:
- Around line 12-13: Remove the redundant standalone indexes supporting the
leading columns of the composite unique keys after confirming no
standalone-column queries require them: remove idx_project_pin_user_id in
src/main/resources/db/migration/V002__project_pin.sql (lines 12-13) and
idx_project_notice_read_notice_id in
src/main/resources/db/migration/V003__project_notice_read.sql (lines 12-14).
In `@src/test/resources/application.yml`:
- Around line 19-20: 추가 테스트 구성을 마련해 MySQL Testcontainers 환경에서 Flyway 마이그레이션을
활성화한 채 V001~V010을 실행하고 검증하세요. 기존 H2 테스트와 spring.flyway.enabled: false 설정은 유지하되,
CI 또는 배포 단계에서 V008 중복 데이터 처리, V009 레거시 enum 값 처리, V010 MySQL SQL 및 데이터 이전을 확인하도록
구성하세요.
🪄 Autofix
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: fc3b0c39-4fa3-4980-87d9-a8652de17a87
📒 Files selected for processing (26)
.env.examplebuild.gradlesrc/main/java/com/slatto/domain/notification/repository/ActivityLogRepository.javasrc/main/java/com/slatto/domain/notification/repository/ProjectLatestActivityProjection.javasrc/main/java/com/slatto/domain/project/service/ProjectService.javasrc/main/java/com/slatto/domain/user/controller/UserController.javasrc/main/java/com/slatto/domain/user/dto/UserProfileImageResponse.javasrc/main/java/com/slatto/domain/user/entity/Users.javasrc/main/java/com/slatto/domain/user/exception/UserErrorCode.javasrc/main/java/com/slatto/domain/user/service/UserService.javasrc/main/resources/application.ymlsrc/main/resources/db/migration/005-project-member-activity-read.sqlsrc/main/resources/db/migration/V001__auth_google_login.sqlsrc/main/resources/db/migration/V002__project_pin.sqlsrc/main/resources/db/migration/V003__project_notice_read.sqlsrc/main/resources/db/migration/V004__notification_group_count.sqlsrc/main/resources/db/migration/V005__recruitment_crud.sqlsrc/main/resources/db/migration/V006__recruitment_application_status.sqlsrc/main/resources/db/migration/V007__notification_project_nullable.sqlsrc/main/resources/db/migration/V008__recruitment_application_active_unique.sqlsrc/main/resources/db/migration/V009__activity_log_type.sqlsrc/main/resources/db/migration/V010__project_activity_read.sqlsrc/test/java/com/slatto/domain/notification/repository/ActivityLogRepositoryIntegrationTest.javasrc/test/java/com/slatto/domain/user/service/PortfolioUpdatedAtTest.javasrc/test/java/com/slatto/domain/user/service/UserProfileImageServiceTest.javasrc/test/resources/application.yml
💤 Files with no reviewable changes (1)
- src/main/resources/db/migration/005-project-member-activity-read.sql
🛑 Comments failed to post (1)
src/main/resources/db/migration/V008__recruitment_application_active_unique.sql (1)
6-11: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash set -euo pipefail echo "== files ==" git ls-files | rg 'src/main/resources/db/migration|V008|recruitment_application|recruitment_application_active_unique|recruitment.*migration|migration' echo "== target file ==" if [ -f src/main/resources/db/migration/V008__recruitment_application_active_unique.sql ]; then cat -n src/main/resources/db/migration/V008__recruitment_application_active_unique.sql fi echo "== related migration files ==" fd -p 'src/main/resources/db/migration' -t f | sort | sed -n '1,120p' echo "== search recruitment_application references ==" rg -n "recruitment_application|deleted_at|active_user_id|uq_recruitment_application_active|INSERT INTO.*recruitment_application|exists|SELECT .*recruitment_application" -S .Repository: SLAT-TO/SLATE-TO-BE
Length of output: 13033
🏁 Script executed:
#!/bin/bash set -euo pipefail python3 - <<'PY' from pathlib import Path p = Path("src/main/resources/db/migration/V008__recruitment_application_active_unique.sql") if p.exists(): text = p.read_text() print("contains generated virtual column:", "GENERATED ALWAYS AS" in text and "VIRTUAL" in text) print("contains unique key add:", "ADD UNIQUE KEY uq_recruitment_application_active" in text) print("contains pre-existing duplicate cleanup select/update/delete:", any(s in text for s in [ "DELETE", "UPDATE", "SELECT recruitment_id, user_id, COUNT(*)", "active_count", "GROUP BY recruitment_id, user_id", "HAVING COUNT(*) > 1" ])) PYRepository: SLAT-TO/SLATE-TO-BE
Length of output: 295
기존 활성 중복 데이터를 정리한 후 유니크 키를 생성하세요.
V008은
deleted_at IS NULL인 활성 지원의recruitment_id와user_id로 유니크 키를 추가합니다. 기존 중복 활성 지원이 있으면 마이그레이션 실행이 실패할 수 있으므로 배포 전에 중복을 확인하고, 발견된 경우 비즈니스 규칙에 맞게 정리하거나 백필한 뒤 이 마이그레이션을 수행하세요.SELECT recruitment_id, user_id, COUNT(*) AS active_count FROM recruitment_application WHERE deleted_at IS NULL GROUP BY recruitment_id, user_id HAVING COUNT(*) > 1;🤖 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/resources/db/migration/V008__recruitment_application_active_unique.sql` around lines 6 - 11, Before creating uq_recruitment_application_active in V008, detect existing duplicate active applications using recruitment_id and user_id, then resolve or backfill them according to the business rules so each active pair is unique. Run the unique-key creation only after the data cleanup has completed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
UserService 충돌 해결. 양쪽이 getUserOrThrow 앞에 서로 다른 private 메서드를 추가해 발생한 위치 충돌이라 둘 다 유지했다. - HEAD: getUserRegions (활동 지역 다중 조회) - develop: 프로필 이미지 업로드 관련 헬퍼 (#125)
🔗 관련 이슈 (Related Issue)
📝 작업 내용
홈 화면의 진행 중인 프로젝트 카드가 프로젝트 수정 시각이 아니라 실제 최근 활동의 최신 발생 시각을 받을 수 있도록 조회 로직을 보완했습니다. 또한 사용자가 프로필 이미지를 업로드하면 S3에 저장하고, 화면에서 바로 사용할 CDN 공개 URL을 반환하는 API를 추가했습니다. 최근활동 읽음 처리 관련 스키마 변경도 Flyway 기반으로 자동화했습니다.
주요 검토 파일
프로젝트 목록 최근활동 시각
src/main/java/com/slatto/domain/notification/repository/ActivityLogRepository.java- 프로젝트별 최신 활동 시각 집계 쿼리 추가src/main/java/com/slatto/domain/project/service/ProjectService.java- 프로젝트 목록의lastActivityAt을 활동 로그 기준으로 변경src/test/java/com/slatto/domain/notification/repository/ActivityLogRepositoryIntegrationTest.java- 프로젝트별 최신 활동 시각 조회 통합 테스트 추가프로필 이미지 업로드
src/main/java/com/slatto/domain/user/controller/UserController.java- 프로필 이미지 업로드 API 추가src/main/java/com/slatto/domain/user/service/UserService.java- 이미지 검증, S3 저장, CDN URL 조합 및 기존 이미지 정리 처리src/main/java/com/slatto/domain/user/entity/Users.java- 프로필 이미지 URL 변경 도메인 메서드 추가src/test/java/com/slatto/domain/user/service/UserProfileImageServiceTest.java- 업로드 성공 및 허용하지 않는 형식 차단 테스트 추가Flyway 마이그레이션 자동화
build.gradle/src/main/resources/application.yml- Flyway 활성화 및 기본 설정 추가src/main/resources/db/migration/V009__activity_log_type.sql-activity_log.type컬럼을VARCHAR(50)로 전환src/main/resources/db/migration/V010__project_activity_read.sql- 최근활동 개별 읽음 테이블 및 조회 인덱스 정리1. 프로젝트 카드 최근활동 시각 정합성
프로젝트 목록 응답의
lastActivityAt을 프로젝트updatedAt이 아닌activity_log.created_at의 최신값으로 반환하도록 변경했습니다.lastActivityAt: null을 반환합니다.lastActivityAt과 현재 시각을 비교해n분 전을 표시하고, 값이null이면 문구를 숨깁니다.2. 프로필 이미지 업로드 API
file필드로 JPG, PNG, WebP 이미지를 최대 10MB까지 업로드할 수 있습니다.users/{userId}/profile-images/{uuid}.{extension}경로로 저장합니다.profileImageUrl로 CDN 공개 URL을 반환합니다.3. Flyway 기반 마이그레이션 자동화
자동 마이그레이션을 Flyway로 전환했고, 중복된 버전 번호도 정리했습니다.
ddl-auto=validate는 DB를 변경하지 않고 구조만 검증하도록 구성했습니다.V8기준으로 baseline 처리한 뒤 이후V009,V010만 자동 적용하도록 정리했습니다.activity_log.type을VARCHAR(50)으로 전환하는V009를 추가했습니다.V010으로 정리했습니다.last_activity_read_at마이그레이션은 개별 읽음 테이블 방식으로 대체돼 제거했습니다.create-drop방식을 유지하도록 Flyway를 비활성화했습니다../gradlew compileJava,./gradlew test모두 통과했습니다.3. 테스트 및 검증
createdAt만 조회되는지 통합 테스트를 추가했습니다.null로 처리되는지 확인했습니다.StorageServicemock을 추가해 전체 테스트 환경을 보완했습니다.✅ PR 체크리스트
Summary by CodeRabbit