Repository navigation
feat: 팀 서비스 이미지 관리 기능 추가 - #218
Conversation
📝 WalkthroughWalkthrough팀 서비스에 썸네일 이미지 기능을 추가한다. OCI Object Storage 인프라(인터페이스, 구현체, 트랜잭션 동기화 삭제)를 도입하고, Changes팀 서비스 썸네일 이미지 및 OCI 객체 스토리지 연동
Sequence Diagram(s)sequenceDiagram
participant Client
participant AdminTeamServiceController
participant AdminTeamServiceManageService
participant ObjectStorageManager
participant TeamServiceImageCommandService
participant ObjectStorageTransactionSynchronizer
rect rgba(70, 130, 180, 0.5)
Note over Client, ObjectStorageTransactionSynchronizer: 팀 서비스 생성(썸네일 포함)
Client->>AdminTeamServiceController: POST /team-services (multipart: request + thumbnailImage)
AdminTeamServiceController->>AdminTeamServiceManageService: createTeamService(request, thumbnailImage)
AdminTeamServiceManageService->>AdminTeamServiceManageService: buildObjectKey(file, serviceId) — 확장자/MIME/크기 검증
AdminTeamServiceManageService->>ObjectStorageManager: upload(file, objectKey)
AdminTeamServiceManageService->>ObjectStorageTransactionSynchronizer: registerDeleteOnRollback(newKey)
AdminTeamServiceManageService->>TeamServiceImageCommandService: save(thumbnailEntity)
AdminTeamServiceController-->>Client: 201 Created (Location 헤더)
end
rect rgba(34, 139, 34, 0.5)
Note over Client, ObjectStorageTransactionSynchronizer: 팀 서비스 수정(새 썸네일로 교체)
Client->>AdminTeamServiceController: PUT /team-services (multipart: request + thumbnailImage)
AdminTeamServiceController->>AdminTeamServiceManageService: updateTeamService(request, thumbnailImage)
AdminTeamServiceManageService->>ObjectStorageManager: upload(file, newObjectKey)
AdminTeamServiceManageService->>ObjectStorageTransactionSynchronizer: registerDeleteOnRollback(newKey)
AdminTeamServiceManageService->>TeamServiceImageCommandService: updateObjectKey(thumbnailEntity, newKey)
AdminTeamServiceManageService->>ObjectStorageTransactionSynchronizer: registerDeleteAfterCommit(oldKey)
AdminTeamServiceController-->>Client: 204 No Content
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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: 1
🧹 Nitpick comments (2)
src/main/resources/db/migration/V6__add_team_service_image_table.sql (1)
1-11: 조회 패턴에 맞는 인덱스 및 썸네일 유일성 고려.
findByTeamServiceAndIsThumbnailTrue/findByTeamServiceIdInAndIsThumbnailTrue조회가TEAM_SERVICE_ID,IS_THUMBNAIL기준으로 동작합니다.TEAM_SERVICE_ID(또는(TEAM_SERVICE_ID, IS_THUMBNAIL)) 인덱스를 추가하면 조회 성능에 도움이 됩니다. 또한findThumbnailUrls의associateBy는 서비스당 썸네일이 둘 이상일 경우 마지막 값만 남기므로, 서비스당 썸네일을 하나로 보장하려면 부분 유니크 제약/인덱스 도입을 검토해 보세요.🤖 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/V6__add_team_service_image_table.sql` around lines 1 - 11, Add an index and a uniqueness guard for thumbnail lookups: create an index on TEAM_SERVICE_IMAGES for the lookup columns (at least TEAM_SERVICE_ID, or better the composite (TEAM_SERVICE_ID, IS_THUMBNAIL)) to speed findByTeamServiceAndIsThumbnailTrue and findByTeamServiceIdInAndIsThumbnailTrue, and enforce at-most-one-thumbnail-per-service by adding a uniqueness constraint/index that applies only when IS_THUMBNAIL = 1 (e.g., a function-based unique index or equivalent filtered/partial unique index on TEAM_SERVICE_ID where IS_THUMBNAIL = 1) so findThumbnailUrls' associateBy won’t drop duplicates; implement these changes in the TEAM_SERVICE_IMAGES DDL (adjust V6__add_team_service_image_table.sql) alongside the FK constraint.src/test/kotlin/co/yappuworld/team/client/application/AdminTeamServiceManageServiceTest.kt (1)
104-141: ⚡ Quick win업데이트 시 썸네일 생성 시나리오 테스트 추가를 고려해보세요.
현재 테스트는 기존 썸네일을 수정하거나 삭제하는 경우를 검증하지만, 썸네일이 없던 서비스에 새로 썸네일을 추가하는 경우는 검증하지 않습니다.
updateThumbnail로직에서thumbnail?.updateImageUrl(it) ?: createThumbnail(service, it)부분의createThumbnail분기를 명시적으로 검증하는 테스트를 추가하면 더욱 완전한 커버리지를 확보할 수 있습니다.제안하는 추가 테스트 시나리오
scenario("썸네일이 없던 서비스에 썸네일을 추가한다") { val team = teamRepository.save(getTeamEntityFixture()) val service = teamServiceRepository.save(getTeamServiceEntityFixture(team = team)) adminTeamServiceManageService.updateTeamService( updateRequest( id = service.id, teamId = team.id, thumbnailImageUrl = "https://image.yapp.co.kr/new.png" ) ) val thumbnail = teamServiceImageRepository.findByTeamServiceAndIsThumbnailTrue(service) thumbnail?.imageUrl shouldBe "https://image.yapp.co.kr/new.png" }🤖 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/kotlin/co/yappuworld/team/client/application/AdminTeamServiceManageServiceTest.kt` around lines 104 - 141, Test coverage misses the branch where updateThumbnail creates a new thumbnail; add a scenario to AdminTeamServiceManageServiceTest that calls adminTeamServiceManageService.updateTeamService with a thumbnailImageUrl when the TeamService has no existing thumbnail to assert createThumbnail branch behavior. Specifically, add a scenario similar to the others that saves a team and service without a thumbnail, invokes updateTeamService (use the existing updateRequest helper) with thumbnailImageUrl set (e.g. "https://image.yapp.co.kr/new.png"), then assert that teamServiceImageRepository.findByTeamServiceAndIsThumbnailTrue(service)?.imageUrl equals that URL to validate the updateThumbnail -> createThumbnail path is exercised.
🤖 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/resources/db/migration/V6__add_team_service_image_table.sql`:
- Line 4: The IMAGE_URL column in the migration uses VARCHAR2(3000) but
TeamServiceImageEntity (length = 2000) and schema.sql (varchar(2000)) use 2000;
update the migration's IMAGE_URL definition from VARCHAR2(3000) to
VARCHAR2(2000) so all three places (V6__add_team_service_image_table.sql
IMAGE_URL, TeamServiceImageEntity length, and schema.sql) use the same 2000
length.
---
Nitpick comments:
In `@src/main/resources/db/migration/V6__add_team_service_image_table.sql`:
- Around line 1-11: Add an index and a uniqueness guard for thumbnail lookups:
create an index on TEAM_SERVICE_IMAGES for the lookup columns (at least
TEAM_SERVICE_ID, or better the composite (TEAM_SERVICE_ID, IS_THUMBNAIL)) to
speed findByTeamServiceAndIsThumbnailTrue and
findByTeamServiceIdInAndIsThumbnailTrue, and enforce
at-most-one-thumbnail-per-service by adding a uniqueness constraint/index that
applies only when IS_THUMBNAIL = 1 (e.g., a function-based unique index or
equivalent filtered/partial unique index on TEAM_SERVICE_ID where IS_THUMBNAIL =
1) so findThumbnailUrls' associateBy won’t drop duplicates; implement these
changes in the TEAM_SERVICE_IMAGES DDL (adjust
V6__add_team_service_image_table.sql) alongside the FK constraint.
In
`@src/test/kotlin/co/yappuworld/team/client/application/AdminTeamServiceManageServiceTest.kt`:
- Around line 104-141: Test coverage misses the branch where updateThumbnail
creates a new thumbnail; add a scenario to AdminTeamServiceManageServiceTest
that calls adminTeamServiceManageService.updateTeamService with a
thumbnailImageUrl when the TeamService has no existing thumbnail to assert
createThumbnail branch behavior. Specifically, add a scenario similar to the
others that saves a team and service without a thumbnail, invokes
updateTeamService (use the existing updateRequest helper) with thumbnailImageUrl
set (e.g. "https://image.yapp.co.kr/new.png"), then assert that
teamServiceImageRepository.findByTeamServiceAndIsThumbnailTrue(service)?.imageUrl
equals that URL to validate the updateThumbnail -> createThumbnail path is
exercised.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: d130b51d-ebf8-4a47-b1ce-8c8d8fed3607
📒 Files selected for processing (18)
src/main/kotlin/co/yappuworld/team/client/application/AdminTeamServiceManageService.ktsrc/main/kotlin/co/yappuworld/team/client/application/HistoricalServiceService.ktsrc/main/kotlin/co/yappuworld/team/client/dto/request/AdminTeamServiceCreateRequest.ktsrc/main/kotlin/co/yappuworld/team/client/dto/request/AdminTeamServiceUpdateRequest.ktsrc/main/kotlin/co/yappuworld/team/client/dto/response/AdminTeamServiceDetailResponse.ktsrc/main/kotlin/co/yappuworld/team/client/dto/response/HistoricalServiceDetailResponse.ktsrc/main/kotlin/co/yappuworld/team/client/dto/response/HistoricalServicePageResponse.ktsrc/main/kotlin/co/yappuworld/team/client/presentation/AdminTeamServiceApi.ktsrc/main/kotlin/co/yappuworld/team/client/presentation/HistoricalServiceApi.ktsrc/main/kotlin/co/yappuworld/team/infrastructure/TeamServiceImageCommandService.ktsrc/main/kotlin/co/yappuworld/team/infrastructure/TeamServiceImageFindService.ktsrc/main/kotlin/co/yappuworld/team/infrastructure/entity/TeamServiceImageEntity.ktsrc/main/kotlin/co/yappuworld/team/infrastructure/jpa/TeamServiceImageRepository.ktsrc/main/resources/db/migration/V6__add_team_service_image_table.sqlsrc/main/resources/schema.sqlsrc/test/kotlin/co/yappuworld/support/fixture/TeamFixture.ktsrc/test/kotlin/co/yappuworld/team/client/application/AdminTeamServiceManageServiceTest.ktsrc/test/kotlin/co/yappuworld/team/client/application/HistoricalServiceServiceTest.kt
devmizz
left a comment
There was a problem hiding this comment.
고생하셨습니다!
지금 작업하신 내용은 클라이언트에서 직접 저장소에 업로드 하는 걸 전제로 해서, URL 체크만 하는 것으로 보여요! 그럴 경우에 두 가지 정도 이슈가 있을 수 있을 거 같아요.
하나는, 이미지에 대한 검수를 저희가 진행할 수 없어요. 용량이나 파일 형식 그리고 이미지에 대한 검증을 진행해야 하는 경우에 서버에서 개입이 불가해요. 프론트에서도 너무 고도화 된 내용은 처리를 할 수가 없고요.
다른 하나는, 이미지 저장소 내 고아 객체에 대한 관리를 할 수가 없어요. 가령 어드민에서 글을 쓰면서 이미지를 업로드했는데 취소를 해버리는 경우, 이미지만 저장소에 업로드 되고 실제 글은 저장이 되면서 사용하지 않는 이미지가 저장소에 남게 돼요.
현재 방식으로 처리하게 되면 위의 문제들을 해결하기가 어려운 부분이 있어서, 이 부분에 대해 조금 고민을 해보시면 좋을 거 같아요!
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/main/resources/db/migration/V6__add_team_service_image_table.sql (1)
5-11:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift썸네일 단일성에 대한 DB 제약이 없어 중복 레코드가 발생할 수 있습니다.
현재 스키마로는 동일
TEAM_SERVICE_ID에IS_THUMBNAIL = 1행이 여러 개 저장될 수 있습니다. 이 경우 단건 조회 경로에서 런타임 예외가 발생하거나, 목록 매핑에서 임의 값으로 덮어써질 수 있습니다.team_service당 썸네일 1개를 DB 레벨에서 강제해 주세요.제안 패치
CREATE TABLE TEAM_SERVICE_IMAGES ( ID RAW(16) PRIMARY KEY, TEAM_SERVICE_ID RAW(16) NOT NULL, OBJECT_KEY VARCHAR2(2000) NOT NULL, IS_THUMBNAIL NUMBER(1) DEFAULT 0 NOT NULL CHECK (IS_THUMBNAIL IN (0, 1)), DISPLAY_ORDER NUMBER DEFAULT 0 NOT NULL, CREATED_AT TIMESTAMP, UPDATED_AT TIMESTAMP, CONSTRAINT FK_TEAM_SERVICE_IMAGES_TEAM_SERVICE FOREIGN KEY (TEAM_SERVICE_ID) REFERENCES TEAM_SERVICES(ID) ON DELETE CASCADE ); + +CREATE UNIQUE INDEX UK_TEAM_SERVICE_IMAGES_THUMBNAIL + ON TEAM_SERVICE_IMAGES ( + CASE WHEN IS_THUMBNAIL = 1 THEN TEAM_SERVICE_ID END + );🤖 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/V6__add_team_service_image_table.sql` around lines 5 - 11, 현재 TEAM_SERVICE_IMAGES 스키마는 동일 TEAM_SERVICE_ID에 IS_THUMBNAIL = 1인 행이 여러 개 들어올 수 있으므로, TEAM_SERVICE_ID 당 썸네일을 1개로 DB 레벨에서 강제하도록 수정하세요: V6__add_team_service_image_table.sql에 TEAM_SERVICE_IMAGES 테이블 정의 후 TEAM_SERVICE_ID와 IS_THUMBNAIL 컬럼을 사용해 유일성 제약을 추가합니다 — Oracle 환경이라면 "CREATE UNIQUE INDEX UX_TEAM_SERVICE_IMAGES_TEAM_SERVICE_THUMBNAIL ON TEAM_SERVICE_IMAGES (CASE WHEN IS_THUMBNAIL = 1 THEN TEAM_SERVICE_ID ELSE NULL END);" 또는 가능한 DB에 맞는 partial unique index / filtered unique constraint 문법을 사용해 IS_THUMBNAIL = 1인 경우에만 TEAM_SERVICE_ID가 유일하도록 만드세요.
🧹 Nitpick comments (2)
src/main/kotlin/co/yappuworld/team/infrastructure/TeamServiceImageFindService.kt (1)
24-27: ⚡ Quick win빈 ID 목록은 조기 반환으로 DB 조회를 건너뛰어 주세요.
teamServiceIds가 비어있을 때는 결과가 항상 빈 맵이므로 즉시 반환하면 불필요한 쿼리를 줄일 수 있습니다.제안 패치
- fun findThumbnailObjectKeys(teamServiceIds: List<UUID>): Map<UUID, String> = - teamServiceImageRepository - .findThumbnailObjectKeysByTeamServiceIdIn(teamServiceIds) - .associateBy({ it.teamServiceId }, { it.objectKey }) + fun findThumbnailObjectKeys(teamServiceIds: List<UUID>): Map<UUID, String> { + if (teamServiceIds.isEmpty()) return emptyMap() + return teamServiceImageRepository + .findThumbnailObjectKeysByTeamServiceIdIn(teamServiceIds) + .associateBy({ it.teamServiceId }, { it.objectKey }) + }🤖 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/kotlin/co/yappuworld/team/infrastructure/TeamServiceImageFindService.kt` around lines 24 - 27, The function findThumbnailObjectKeys should early-return an empty map when teamServiceIds is empty to avoid unnecessary DB queries; update findThumbnailObjectKeys to check if teamServiceIds.isEmpty() and return emptyMap() before calling teamServiceImageRepository.findThumbnailObjectKeysByTeamServiceIdIn(teamServiceIds), keeping the rest of the logic (associateBy using it.teamServiceId and it.objectKey) unchanged.src/test/kotlin/co/yappuworld/team/client/application/AdminTeamServiceManageServiceTest.kt (1)
196-220: ⚡ Quick win파일 업로드와
removeThumbnail=true동시 요청 실패 케이스를 테스트에 추가해 주세요.서비스 구현에서
thumbnailImage != null && removeThumbnail이면BusinessException(TeamError.INVALID_THUMBNAIL_REQUEST)를 던지므로, 이 분기 회귀를 막는 시나리오가 있으면 더 안전합니다.🧪 제안 테스트 추가안
feature("어드민 팀 서비스 수정") { + scenario("썸네일 이미지와 삭제 요청을 동시에 보내면 예외가 발생한다") { + val team = teamRepository.save(getTeamEntityFixture()) + val service = teamServiceRepository.save(getTeamServiceEntityFixture(team = team)) + + shouldThrow<BusinessException> { + adminTeamServiceManageService.updateTeamService( + updateRequest( + id = service.id, + teamId = team.id, + removeThumbnail = true + ), + thumbnailImage("conflict.png") + ) + }.error shouldBe TeamError.INVALID_THUMBNAIL_REQUEST + } + scenario("기존 썸네일이 있으면 업로드 URL로 변경한다") {🤖 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/kotlin/co/yappuworld/team/client/application/AdminTeamServiceManageServiceTest.kt` around lines 196 - 220, Add a new scenario to the "어드민 팀 서비스 수정" feature that covers the invalid simultaneous upload+remove case: create a team and team service fixture as in the existing scenario, then call adminTeamServiceManageService.updateTeamService with an Update request that includes both a non-null thumbnailImage (use the existing thumbnailImage("file.png") helper) and removeThumbnail = true, and assert that the call throws BusinessException with TeamError.INVALID_THUMBNAIL_REQUEST; reuse the same transaction setup (newTransactionTemplate().executeWithoutResult { ... }) and helpers (getTeamEntityFixture, getTeamServiceEntityFixture) so the test mirrors the existing flow and prevents regressions in the branch where thumbnailImage != null && removeThumbnail.
🤖 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/kotlin/co/yappuworld/team/client/application/AdminTeamServiceManageService.kt`:
- Around line 150-162: The current buildObjectKey(serviceId: UUID, file:
MultipartFile) only checks filename extension and size, which allows disguised
non-image uploads; update this function to also validate the uploaded file's
actual content by checking the MultipartFile.contentType (MIME) against allowed
image MIME types and performing a magic-byte signature check on the file's first
bytes (read a small header byte[] from file.inputStream) before accepting; keep
existing size and extension checks, and throw
BusinessException(TeamError.INVALID_IMAGE) if MIME or signature validation
fails; use the same validation approach for the other upload validation spots
referenced (lines ~167-169) so all image upload paths use consistent MIME +
magic-byte + size checks with ALLOWED_IMAGE_EXTENSIONS and MAX_IMAGE_FILE_SIZE.
---
Outside diff comments:
In `@src/main/resources/db/migration/V6__add_team_service_image_table.sql`:
- Around line 5-11: 현재 TEAM_SERVICE_IMAGES 스키마는 동일 TEAM_SERVICE_ID에 IS_THUMBNAIL
= 1인 행이 여러 개 들어올 수 있으므로, TEAM_SERVICE_ID 당 썸네일을 1개로 DB 레벨에서 강제하도록 수정하세요:
V6__add_team_service_image_table.sql에 TEAM_SERVICE_IMAGES 테이블 정의 후
TEAM_SERVICE_ID와 IS_THUMBNAIL 컬럼을 사용해 유일성 제약을 추가합니다 — Oracle 환경이라면 "CREATE
UNIQUE INDEX UX_TEAM_SERVICE_IMAGES_TEAM_SERVICE_THUMBNAIL ON
TEAM_SERVICE_IMAGES (CASE WHEN IS_THUMBNAIL = 1 THEN TEAM_SERVICE_ID ELSE NULL
END);" 또는 가능한 DB에 맞는 partial unique index / filtered unique constraint 문법을 사용해
IS_THUMBNAIL = 1인 경우에만 TEAM_SERVICE_ID가 유일하도록 만드세요.
---
Nitpick comments:
In
`@src/main/kotlin/co/yappuworld/team/infrastructure/TeamServiceImageFindService.kt`:
- Around line 24-27: The function findThumbnailObjectKeys should early-return an
empty map when teamServiceIds is empty to avoid unnecessary DB queries; update
findThumbnailObjectKeys to check if teamServiceIds.isEmpty() and return
emptyMap() before calling
teamServiceImageRepository.findThumbnailObjectKeysByTeamServiceIdIn(teamServiceIds),
keeping the rest of the logic (associateBy using it.teamServiceId and
it.objectKey) unchanged.
In
`@src/test/kotlin/co/yappuworld/team/client/application/AdminTeamServiceManageServiceTest.kt`:
- Around line 196-220: Add a new scenario to the "어드민 팀 서비스 수정" feature that
covers the invalid simultaneous upload+remove case: create a team and team
service fixture as in the existing scenario, then call
adminTeamServiceManageService.updateTeamService with an Update request that
includes both a non-null thumbnailImage (use the existing
thumbnailImage("file.png") helper) and removeThumbnail = true, and assert that
the call throws BusinessException with TeamError.INVALID_THUMBNAIL_REQUEST;
reuse the same transaction setup (newTransactionTemplate().executeWithoutResult
{ ... }) and helpers (getTeamEntityFixture, getTeamServiceEntityFixture) so the
test mirrors the existing flow and prevents regressions in the branch where
thumbnailImage != null && removeThumbnail.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 1008fd5f-2aff-4865-ac0e-5d3ffcb3b1fe
📒 Files selected for processing (29)
.github/workflows/yappu-oci-dev-cd.yaml.github/workflows/yappu-oci-prod-cd.yamlbuild.gradle.ktsgradle/libs.versions.tomlsrc/main/kotlin/co/yappuworld/external/storage/DisabledObjectStorageService.ktsrc/main/kotlin/co/yappuworld/external/storage/ObjectStorageService.ktsrc/main/kotlin/co/yappuworld/external/storage/ObjectStorageTransactionSynchronizer.ktsrc/main/kotlin/co/yappuworld/external/storage/OciObjectStorageConfig.ktsrc/main/kotlin/co/yappuworld/external/storage/OciObjectStorageProperties.ktsrc/main/kotlin/co/yappuworld/external/storage/OciObjectStorageService.ktsrc/main/kotlin/co/yappuworld/team/client/application/AdminTeamServiceManageService.ktsrc/main/kotlin/co/yappuworld/team/client/application/HistoricalServiceService.ktsrc/main/kotlin/co/yappuworld/team/client/dto/request/AdminTeamServiceUpdateRequest.ktsrc/main/kotlin/co/yappuworld/team/client/presentation/AdminTeamServiceApi.ktsrc/main/kotlin/co/yappuworld/team/client/presentation/AdminTeamServiceController.ktsrc/main/kotlin/co/yappuworld/team/domain/vo/TeamError.ktsrc/main/kotlin/co/yappuworld/team/infrastructure/TeamServiceImageFindService.ktsrc/main/kotlin/co/yappuworld/team/infrastructure/entity/TeamServiceImageEntity.ktsrc/main/kotlin/co/yappuworld/team/infrastructure/jpa/TeamServiceImageRepository.ktsrc/main/resources/application-dev-oci.yamlsrc/main/resources/application-local.yamlsrc/main/resources/application-prod-oci.yamlsrc/main/resources/application-test.yamlsrc/main/resources/db/migration/V6__add_team_service_image_table.sqlsrc/main/resources/schema.sqlsrc/test/kotlin/co/yappuworld/support/fixture/TeamFixture.ktsrc/test/kotlin/co/yappuworld/support/storage/FakeObjectStorageService.ktsrc/test/kotlin/co/yappuworld/team/client/application/AdminTeamServiceManageServiceTest.ktsrc/test/kotlin/co/yappuworld/team/client/application/HistoricalServiceServiceTest.kt
✅ Files skipped from review due to trivial changes (4)
- src/main/resources/application-prod-oci.yaml
- src/test/kotlin/co/yappuworld/support/storage/FakeObjectStorageService.kt
- src/main/resources/application-test.yaml
- src/main/kotlin/co/yappuworld/external/storage/ObjectStorageTransactionSynchronizer.kt
🚧 Files skipped from review as they are similar to previous changes (4)
- src/main/resources/schema.sql
- src/test/kotlin/co/yappuworld/support/fixture/TeamFixture.kt
- src/main/kotlin/co/yappuworld/team/client/application/HistoricalServiceService.kt
- src/test/kotlin/co/yappuworld/team/client/application/HistoricalServiceServiceTest.kt
devmizz
left a comment
There was a problem hiding this comment.
수정하느라 고생 많으셨습니다! 두 가지 추가 사항이 있습니다.
- 팀 삭제 시에 서비스 이미지도 삭제되는지 확인 필요
- OCI Storage 비용 구조 어떻게 되는지 확인 필요
| name = ["enabled"], | ||
| havingValue = "false" | ||
| ) | ||
| class DisabledObjectStorageService : ObjectStorageService { |
There was a problem hiding this comment.
C) 요건 왜 있는 건가요? Disabled 접두사가 있어서, 비활성화된 거면 왜 구현체로 있는지 이해가 안 됩니다! NoUsage라서 더더욱이요!
There was a problem hiding this comment.
로컬, 테스트 환경에서는 oci를 사용하지 않지만, ObjectStorageService 의존성 주입을 유지하기 위한 대체 구현체로 추가했습니다!
There was a problem hiding this comment.
@eeun1225 그런 의도라면 Mock이나 Fake 같은 용어를 쓰면 좋을 거 같아요!
| var teamService: TeamServiceEntity = teamService | ||
| private set | ||
|
|
||
| @Column(name = "object_key", nullable = false, length = 2000) |
There was a problem hiding this comment.
A) 이건 기존에 image_url 이었던 거 같은데, object_key로 변경하신 이유가 있을까요? image 테이블이라 image_url 해도 큰 지장은 없어보여서요!
There was a problem hiding this comment.
dev/prod 버킷을 분리해 환경별 base url이 다르고, 추후 도메인이나 버킷 경로 변경 시 DB 데이터까지 수정해야 하는 문제를 방지하기 위해 objectKey만 저장하고 조회 시 url을 생성하도록 변경했습니다!
There was a problem hiding this comment.
@eeun1225 key가 혹시 버킷 경로(패키지 경로)를 제외한 파일명만 해당하는 건가요?
There was a problem hiding this comment.
아니요! team-services/{serviceId}/{uuid}.{extension} 형태로 버킷 내부 경로까지 포함한 값으로 저장하고 있습니당
|
|
||
| import org.springframework.web.multipart.MultipartFile | ||
|
|
||
| interface ObjectStorageService { |
There was a problem hiding this comment.
C) Manager 라는 이름은 어떨까요? 업로드랑 삭제를 하면서 객체들을 관리한다는 점에서요!
|
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/main/kotlin/co/yappuworld/external/storage/MockObjectStorageManager.kt (1)
16-20:⚠️ Potential issue | 🟠 Major | ⚡ Quick win비활성 스토리지 경로에서 의도된 상태를 500으로 노출하고 있습니다.
Line 19에서
INTERNAL_SERVER_ERROR를 고정으로 던지면,object-storage.enabled=false프로필에서 썸네일 업로드 요청이 설정 기반 제한임에도 항상 일반 500으로 처리됩니다. 비활성화 상태를 나타내는 명시적 에러(예: 기능 비활성/설정 오류 전용 코드)로 분기해 API 의미를 분리하는 게 안전합니다.🤖 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/kotlin/co/yappuworld/external/storage/MockObjectStorageManager.kt` around lines 16 - 20, The upload method in MockObjectStorageManager is throwing a generic INTERNAL_SERVER_ERROR, but when the object storage is disabled via configuration (object-storage.enabled=false), this misleads clients into thinking there's an actual server error rather than a feature limitation. Replace the INTERNAL_SERVER_ERROR with a more specific and semantically appropriate error code from GlobalError that indicates the storage feature is disabled or unavailable (such as a feature disabled error or configuration error code). This way, the API response accurately reflects the true nature of the issue: a configuration-based restriction rather than an actual internal server error.
🧹 Nitpick comments (1)
src/test/kotlin/co/yappuworld/team/client/application/AdminTeamServiceTest.kt (1)
191-191: 💤 Low value삭제 순서에 의존하는 단언문이 테스트 불안정성을 유발할 수 있습니다.
shouldBe listOf(firstObjectKey, secondObjectKey)는 삭제 순서가 특정 순서임을 가정합니다.findObjectKeys의 반환 순서가 보장되지 않으면 테스트가 간헐적으로 실패할 수 있습니다. 순서에 무관한 검증을 권장합니다.♻️ 순서 무관 검증으로 변경
-objectStorageManager.deletedObjectKeys shouldBe listOf(firstObjectKey, secondObjectKey) +objectStorageManager.deletedObjectKeys shouldContainExactlyInAnyOrder listOf(firstObjectKey, secondObjectKey)
shouldContainExactlyInAnyOrder사용을 위해 import 추가:import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder🤖 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/kotlin/co/yappuworld/team/client/application/AdminTeamServiceTest.kt` at line 191, The assertion in AdminTeamServiceTest.kt using shouldBe with a specific list order (firstObjectKey, secondObjectKey) depends on deletion order which is not guaranteed, causing potential test instability. Replace this order-dependent assertion with shouldContainExactlyInAnyOrder to verify that the deleted object keys contain exactly the expected elements regardless of order. Add the necessary import for shouldContainExactlyInAnyOrder from io.kotest.matchers.collections.
🤖 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.
Outside diff comments:
In `@src/main/kotlin/co/yappuworld/external/storage/MockObjectStorageManager.kt`:
- Around line 16-20: The upload method in MockObjectStorageManager is throwing a
generic INTERNAL_SERVER_ERROR, but when the object storage is disabled via
configuration (object-storage.enabled=false), this misleads clients into
thinking there's an actual server error rather than a feature limitation.
Replace the INTERNAL_SERVER_ERROR with a more specific and semantically
appropriate error code from GlobalError that indicates the storage feature is
disabled or unavailable (such as a feature disabled error or configuration error
code). This way, the API response accurately reflects the true nature of the
issue: a configuration-based restriction rather than an actual internal server
error.
---
Nitpick comments:
In
`@src/test/kotlin/co/yappuworld/team/client/application/AdminTeamServiceTest.kt`:
- Line 191: The assertion in AdminTeamServiceTest.kt using shouldBe with a
specific list order (firstObjectKey, secondObjectKey) depends on deletion order
which is not guaranteed, causing potential test instability. Replace this
order-dependent assertion with shouldContainExactlyInAnyOrder to verify that the
deleted object keys contain exactly the expected elements regardless of order.
Add the necessary import for shouldContainExactlyInAnyOrder from
io.kotest.matchers.collections.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: c5354146-fcad-4568-b5cf-51f78206582a
📒 Files selected for processing (14)
src/main/kotlin/co/yappuworld/external/storage/MockObjectStorageManager.ktsrc/main/kotlin/co/yappuworld/external/storage/ObjectStorageManager.ktsrc/main/kotlin/co/yappuworld/external/storage/ObjectStorageTransactionSynchronizer.ktsrc/main/kotlin/co/yappuworld/external/storage/OciObjectStorageManager.ktsrc/main/kotlin/co/yappuworld/team/client/application/AdminTeamService.ktsrc/main/kotlin/co/yappuworld/team/client/application/AdminTeamServiceManageService.ktsrc/main/kotlin/co/yappuworld/team/client/application/HistoricalServiceService.ktsrc/main/kotlin/co/yappuworld/team/infrastructure/TeamServiceImageFindService.ktsrc/main/resources/application-dev-oci.yamlsrc/main/resources/application-prod-oci.yamlsrc/test/kotlin/co/yappuworld/support/storage/FakeObjectStorageManager.ktsrc/test/kotlin/co/yappuworld/team/client/application/AdminTeamServiceManageServiceTest.ktsrc/test/kotlin/co/yappuworld/team/client/application/AdminTeamServiceTest.ktsrc/test/kotlin/co/yappuworld/team/client/application/HistoricalServiceServiceTest.kt
✅ Files skipped from review due to trivial changes (2)
- src/main/kotlin/co/yappuworld/external/storage/ObjectStorageManager.kt
- src/test/kotlin/co/yappuworld/support/storage/FakeObjectStorageManager.kt
🚧 Files skipped from review as they are similar to previous changes (7)
- src/main/resources/application-dev-oci.yaml
- src/main/kotlin/co/yappuworld/external/storage/ObjectStorageTransactionSynchronizer.kt
- src/main/kotlin/co/yappuworld/team/client/application/HistoricalServiceService.kt
- src/main/kotlin/co/yappuworld/team/infrastructure/TeamServiceImageFindService.kt
- src/main/kotlin/co/yappuworld/team/client/application/AdminTeamServiceManageService.kt
- src/test/kotlin/co/yappuworld/team/client/application/HistoricalServiceServiceTest.kt
- src/test/kotlin/co/yappuworld/team/client/application/AdminTeamServiceManageServiceTest.kt
📌 Related Issue
🚨 해결하려는 문제가 무엇인가요?
⭐️ 어떻게 해결했나요?
OCI Object Storage 기반 이미지 저장 구조를 도입해 팀 서비스 이미지를 안정적으로 관리할 수 있도록 했습니다.
TeamServiceImageEntity를 추가했습니다.thumbnailImageUrl을 포함했습니다.🤔 어떤 부분에 집중하여 리뷰해야 할까요?
🗒️ 참고자료
RCA 룰
Summary by CodeRabbit
릴리스 노트