Skip to content

feat: 유저 태그 수정 및 삭제 시 이미지 태그 반영#135

Merged
leeedohyun merged 4 commits intodevelopfrom
feature/#134-user-tag-sync-to-image-tag
Nov 9, 2025
Merged

feat: 유저 태그 수정 및 삭제 시 이미지 태그 반영#135
leeedohyun merged 4 commits intodevelopfrom
feature/#134-user-tag-sync-to-image-tag

Conversation

@leeedohyun
Copy link
Member

@leeedohyun leeedohyun commented Nov 4, 2025

📌 관련 이슈 (필수)

이 PR이 어떤 이슈를 해결하는지 작성해주세요. 예시: closes #123, resolves #456

📝 작업 내용 (필수)

이번 PR에서 작업한 내용을 간략히 설명해주세요.

유저 태그 수정 및 삭제할 때 이미지 태그에도 반영이 되도록 했습니다.

💬 리뷰 참고 사항 (선택)

리뷰어가 참고할 만한 사항이나 리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요.

Summary by CodeRabbit

  • New Features

    • Tag changes (rename/delete) now synchronize with image tags so tag edits are reflected across the app.
    • Increased per-user tag limit from 30 to 40.
  • Tests

    • Added tests covering tag synchronization, batch tag reassignments, multi-user scenarios, and deletion edge cases.

@leeedohyun leeedohyun self-assigned this Nov 4, 2025
@coderabbitai
Copy link

coderabbitai bot commented Nov 4, 2025

Walkthrough

The PR adds an ImageTagService and repository batch-update method, and integrates ImageTagService into UserTagController so user tag updates and deletes also propagate to image-tag records. It also raises the user tag limit from 30 to 40 and updates tests accordingly.

Changes

Cohort / File(s) Summary
Controller Enhancement
capturecat-core/src/main/java/com/capturecat/core/api/user/UserTagController.java
Injects new ImageTagService (new final field); update/delete handlers now call imageTagService.update(...) and imageTagService.delete(...) to synchronize image tags.
New Service Layer
capturecat-core/src/main/java/com/capturecat/core/service/tag/ImageTagService.java
New Spring service with transactional methods update(LoginUser, oldTagId, newTagId) and delete(LoginUser, tagId) that resolve user and perform image-tag updates/deletes.
Repository Enhancement
capturecat-core/src/main/java/com/capturecat/core/domain/tag/ImageTagRepository.java
Added void updateImageTagsForUser(Long userId, Long oldTagId, Long newTagId) — a modifying query to batch-reassign tag IDs for a user's image-tag records.
User Tag Limit Change
capturecat-core/src/main/java/com/capturecat/core/service/user/UserTagService.java
Increased MAX_USER_TAG_COUNT from 30 to 40.
Controller Tests
capturecat-core/src/test/java/com/capturecat/core/api/user/UserTagControllerTest.java
Injects mocked ImageTagService into controller tests; stubs imageTagService.update and imageTagService.delete in relevant tests.
Repository Tests
capturecat-core/src/test/java/com/capturecat/core/domain/tag/ImageTagRepositoryTest.java
Added tests verifying updateImageTagsForUser() affects only target user's images and ignores images without the old tag.
Service Tests
capturecat-core/src/test/java/com/capturecat/core/service/tag/ImageTagServiceTest.java
New unit tests covering update/delete flows, including missing-user and missing-tag error paths; mocks repositories and asserts interactions.
User Service Tests
capturecat-core/src/test/java/com/capturecat/core/service/user/UserTagServiceTest.java
Updated stubbed tag count to 40L to match new MAX_USER_TAG_COUNT boundary in tests.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant UC as UserTagController
    participant UTS as UserTagService
    participant ITS as ImageTagService
    participant ITR as ImageTagRepository
    participant UR as UserRepository
    participant TR as TagRepository

    Note over UC,ITS: Tag update flow
    User->>UC: PUT /user-tags/{id}
    UC->>UTS: update(userTagDto)
    UTS-->>UC: updatedUserTagResponse
    UC->>ITS: update(loginUser, oldTagId, newTagId)
    ITS->>UR: findByUsername(loginUser.username)
    UR-->>ITS: user
    ITS->>ITR: updateImageTagsForUser(user.id, oldTagId, newTagId)
    ITR-->>ITS: update result
    ITS-->>UC: done
    UC-->>User: 200 OK

    Note over UC,ITS: Tag delete flow
    User->>UC: DELETE /user-tags/{id}
    UC->>UTS: delete(tagId)
    UTS-->>UC: deletionConfirmed
    UC->>ITS: delete(loginUser, tagId)
    ITS->>UR: findByUsername(loginUser.username)
    UR-->>ITS: user
    ITS->>TR: findById(tagId)
    TR-->>ITS: tag
    ITS->>ITR: deleteByTagAndUser(tag, user)
    ITR-->>ITS: deletionResult
    ITS-->>UC: done
    UC-->>User: 200 OK
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

  • Pay attention to transactional boundaries and ordering in ImageTagService.
  • Verify correctness and scope of the repository modifying query (parameter scoping, flush/clear semantics).
  • Check controller integration for error propagation and test coverage alignment.

Possibly related issues

Possibly related PRs

Suggested reviewers

  • jaelyangChoi

Poem

🐰 I hop through code and patch each thread,
When tags move, I nudge images ahead.
Update or delete, I keep them true,
Little hops of data, tidy and new,
A carrot for tests — hooray! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main objective: synchronizing image tags when user tags are modified or deleted.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/#134-user-tag-sync-to-image-tag

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
capturecat-core/src/test/java/com/capturecat/core/api/user/UserTagControllerTest.java (1)

129-147: Fix incorrect mock in delete test.

Line 132 mocks userTagService.create instead of userTagService.delete in a delete test. This appears to be copy-pasted from the create test and should be corrected.

Apply this diff to fix the mock:

 @Test
 void 유저_태그_삭제() {
 	// given
-	BDDMockito.given(userTagService.create(any(), anyString())).willReturn(new TagResponse(1L, "java"));
+	BDDMockito.willDoNothing().given(userTagService).delete(any(), anyLong());
 	BDDMockito.willDoNothing().given(imageTagService).delete(any(), anyLong());
🧹 Nitpick comments (1)
capturecat-core/src/main/java/com/capturecat/core/domain/tag/ImageTagRepository.java (1)

35-37: Document the assumption about newTagId validity.

The bulk update directly modifies the tag.id foreign key, bypassing JPA entity management for performance. While clearAutomatically=true mitigates persistence context issues, this assumes newTagId is valid. Consider adding a JavaDoc comment documenting this precondition:

+/**
+ * Bulk updates image tags for a specific user by replacing oldTagId with newTagId.
+ * @param userId the user whose image tags should be updated
+ * @param oldTagId the current tag ID to replace
+ * @param newTagId the new tag ID (must exist in Tag table)
+ */
 @Modifying(clearAutomatically = true, flushAutomatically = true)
 @Query("UPDATE ImageTag it SET it.tag.id = :newTagId WHERE it.tag.id = :oldTagId AND it.image.user.id = :userId")
 void updateImageTagsForUser(Long userId, Long oldTagId, Long newTagId);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aad4e48 and 0eb792a.

📒 Files selected for processing (6)
  • capturecat-core/src/main/java/com/capturecat/core/api/user/UserTagController.java (3 hunks)
  • capturecat-core/src/main/java/com/capturecat/core/domain/tag/ImageTagRepository.java (1 hunks)
  • capturecat-core/src/main/java/com/capturecat/core/service/tag/ImageTagService.java (1 hunks)
  • capturecat-core/src/test/java/com/capturecat/core/api/user/UserTagControllerTest.java (4 hunks)
  • capturecat-core/src/test/java/com/capturecat/core/domain/tag/ImageTagRepositoryTest.java (1 hunks)
  • capturecat-core/src/test/java/com/capturecat/core/service/tag/ImageTagServiceTest.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
capturecat-core/src/test/java/com/capturecat/core/domain/tag/ImageTagRepositoryTest.java (2)
capturecat-core/src/test/java/com/capturecat/core/DummyObject.java (1)
  • DummyObject (18-100)
capturecat-core/src/test/java/com/capturecat/core/domain/tag/TagFixture.java (1)
  • TagFixture (5-16)
capturecat-core/src/test/java/com/capturecat/core/service/tag/ImageTagServiceTest.java (2)
capturecat-core/src/test/java/com/capturecat/core/DummyObject.java (1)
  • DummyObject (18-100)
capturecat-core/src/test/java/com/capturecat/core/domain/tag/TagFixture.java (1)
  • TagFixture (5-16)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (6)
capturecat-core/src/test/java/com/capturecat/core/domain/tag/ImageTagRepositoryTest.java (1)

118-175: Excellent test coverage for user-scoped tag updates!

Both new tests effectively validate the batch update operation:

  • User isolation is properly tested (only the specified user's tags are updated)
  • Selective updates are verified (only images with the old tag are affected)
  • Proper use of entityManager.flush() and clear() ensures database state is verified
capturecat-core/src/main/java/com/capturecat/core/service/tag/ImageTagService.java (1)

33-41: Delete method logic looks correct.

Proper validation of both user and tag entities before deletion.

capturecat-core/src/main/java/com/capturecat/core/api/user/UserTagController.java (1)

48-57: Update operation order is correct.

Creating/updating the user tag first, then propagating to image tags ensures the new tag ID is available for the batch update.

capturecat-core/src/test/java/com/capturecat/core/api/user/UserTagControllerTest.java (2)

43-49: Test setup correctly integrates ImageTagService.

Proper mock initialization and controller construction with the new dependency.


104-127: Update test properly mocks ImageTagService.

The test correctly stubs imageTagService.update to verify the integration.

capturecat-core/src/test/java/com/capturecat/core/service/tag/ImageTagServiceTest.java (1)

44-114: Comprehensive unit test coverage with excellent error handling verification.

The test suite thoroughly validates:

  • Happy paths for both update and delete operations
  • Proper exception handling when user or tag is not found
  • Repository interaction verification
  • No unintended side effects on failure cases

Comment on lines 59 to 65
@DeleteMapping
public ApiResponse<?> delete(@AuthenticationPrincipal LoginUser loginUser, @RequestParam Long tagId) {
userTagService.delete(loginUser, tagId);
imageTagService.delete(loginUser, tagId);

return ApiResponse.success();
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Reverse the delete operation order to avoid potential errors.

Deleting the user tag before the image tags can cause TAG_NOT_FOUND exception in ImageTagService.delete at line 37-38, which attempts to load the tag after it may have been deleted. This creates fragile transaction-dependent coupling.

Apply this diff to delete image tags first:

 @DeleteMapping
 public ApiResponse<?> delete(@AuthenticationPrincipal LoginUser loginUser, @RequestParam Long tagId) {
-	userTagService.delete(loginUser, tagId);
 	imageTagService.delete(loginUser, tagId);
+	userTagService.delete(loginUser, tagId);
 
 	return ApiResponse.success();
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@DeleteMapping
public ApiResponse<?> delete(@AuthenticationPrincipal LoginUser loginUser, @RequestParam Long tagId) {
userTagService.delete(loginUser, tagId);
imageTagService.delete(loginUser, tagId);
return ApiResponse.success();
}
@DeleteMapping
public ApiResponse<?> delete(@AuthenticationPrincipal LoginUser loginUser, @RequestParam Long tagId) {
imageTagService.delete(loginUser, tagId);
userTagService.delete(loginUser, tagId);
return ApiResponse.success();
}
🤖 Prompt for AI Agents
In
capturecat-core/src/main/java/com/capturecat/core/api/user/UserTagController.java
around lines 59 to 65, the controller currently deletes the user tag before
deleting related image tags which can cause ImageTagService.delete to throw
TAG_NOT_FOUND when it tries to load a tag that has already been removed; modify
the method to call imageTagService.delete(loginUser, tagId) before
userTagService.delete(loginUser, tagId) so image tags are removed while the tag
still exists, and ensure the operation remains within the same transaction (or
annotate with @Transactional at the controller/service level) to keep atomicity.

Comment on lines +25 to +31
@Transactional
public void update(LoginUser loginUser, Long oldTagId, Long newTagId) {
User user = userRepository.findByUsername(loginUser.getUsername())
.orElseThrow(() -> new CoreException(ErrorType.USER_NOT_FOUND));

imageTagRepository.updateImageTagsForUser(user.getId(), oldTagId, newTagId);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Validate that newTagId exists before updating.

The update method doesn't verify that newTagId corresponds to an existing tag. If an invalid ID is passed, the repository update will create orphaned foreign key references, potentially corrupting data.

Apply this diff to add validation:

 @Transactional
 public void update(LoginUser loginUser, Long oldTagId, Long newTagId) {
 	User user = userRepository.findByUsername(loginUser.getUsername())
 		.orElseThrow(() -> new CoreException(ErrorType.USER_NOT_FOUND));
+	tagRepository.findById(newTagId)
+		.orElseThrow(() -> new CoreException(ErrorType.TAG_NOT_FOUND));
 
 	imageTagRepository.updateImageTagsForUser(user.getId(), oldTagId, newTagId);
 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
capturecat-core/src/main/java/com/capturecat/core/service/tag/ImageTagService.java
around lines 25 to 31, the update method currently looks up the User but does
not validate that newTagId exists; add a check that the Tag with newTagId exists
(e.g., tagRepository.findById(newTagId).orElseThrow(() -> new
CoreException(ErrorType.TAG_NOT_FOUND))) before calling
imageTagRepository.updateImageTagsForUser, and throw an appropriate
CoreException if not found so the update cannot create invalid foreign-key
references.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
capturecat-core/src/main/java/com/capturecat/core/service/user/UserTagService.java (1)

33-33: Limit increase looks good, though scope seems unrelated.

The increase from 30 to 40 is straightforward and properly tested. Note that this change appears unrelated to the main PR objective of syncing user tag changes to image tags—consider grouping unrelated changes in separate PRs for clearer review context in the future.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0eb792a and 81aa85f.

📒 Files selected for processing (2)
  • capturecat-core/src/main/java/com/capturecat/core/service/user/UserTagService.java (1 hunks)
  • capturecat-core/src/test/java/com/capturecat/core/service/user/UserTagServiceTest.java (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build
  • GitHub Check: deploy-dev
🔇 Additional comments (1)
capturecat-core/src/test/java/com/capturecat/core/service/user/UserTagServiceTest.java (1)

118-118: Test correctly updated for new limit.

The mock value correctly reflects the updated MAX_USER_TAG_COUNT of 40, properly testing the boundary condition where the user already has 40 tags and attempts to create another.

@leeedohyun leeedohyun merged commit d7497ba into develop Nov 9, 2025
6 checks passed
@leeedohyun leeedohyun deleted the feature/#134-user-tag-sync-to-image-tag branch November 9, 2025 14:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

유저 태그 수정 및 삭제 시 이미지 태그 반영 유저 태그 수정 API

2 participants