Skip to content

feat: 즐겨찾기 등록 API#64

Merged
leeedohyun merged 6 commits intodevelopfrom
feature/#52-bookmark
Jul 8, 2025
Merged

feat: 즐겨찾기 등록 API#64
leeedohyun merged 6 commits intodevelopfrom
feature/#52-bookmark

Conversation

@leeedohyun
Copy link
Member

@leeedohyun leeedohyun commented Jul 8, 2025

📌 관련 이슈 (필수)

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

📝 작업 내용 (필수)

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

즐겨찾기 등록 API를 구현했습니다.

💬 리뷰 참고 사항 (선택)

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

Summary by CodeRabbit

  • New Features

    • Added the ability for users to bookmark images via a new API endpoint.
    • Introduced backend support for bookmark management.
  • Documentation

    • Added comprehensive documentation for the bookmark API, including success and error scenarios.
    • Updated error code documentation to cover bookmark-related errors.
    • Included the new bookmark documentation in the main documentation index.
  • Tests

    • Added tests for the bookmark API controller, service, and entity.
    • Documented error codes for bookmark registration in automated API tests.

@leeedohyun leeedohyun requested a review from jaelyangChoi July 8, 2025 13:12
@leeedohyun leeedohyun self-assigned this Jul 8, 2025
@coderabbitai
Copy link

coderabbitai bot commented Jul 8, 2025

"""

Walkthrough

A new bookmark feature has been introduced, including REST API endpoints, service and repository layers, JPA entity, and comprehensive tests. Documentation for the API and its error codes has been added and integrated into the documentation index. The changes cover both implementation and verification of the bookmark registration process.

Changes

File(s) Change Summary
.../src/main/java/com/capturecat/core/api/bookmark/BookmarkController.java Added new REST controller for bookmark registration.
.../src/main/java/com/capturecat/core/domain/bookmark/Bookmark.java Introduced new JPA entity representing a bookmark between user and image.
.../src/main/java/com/capturecat/core/domain/bookmark/BookmarkRepository.java Added Spring Data JPA repository interface for Bookmark entity with existence check method.
.../src/main/java/com/capturecat/core/service/bookmark/BookmarkService.java Implemented service logic for adding bookmarks, including user and image validation and duplication check.
.../src/main/java/com/capturecat/core/support/error/ErrorCode.java Added new error code constant for bookmark duplication.
.../src/main/java/com/capturecat/core/support/error/ErrorType.java Added new error type for bookmark duplication with HTTP 400 and warning log level.
.../src/docs/asciidoc/bookmark.adoc Added new AsciiDoc documentation for the bookmark API endpoint.
.../src/docs/asciidoc/error-codes.adoc Added section documenting error codes for bookmark registration.
.../src/docs/asciidoc/index.adoc Updated documentation index to include the new bookmark documentation.
.../src/test/java/com/capturecat/core/api/bookmark/BookmarkControllerTest.java Added controller test for bookmark registration endpoint with REST Docs integration.
.../src/test/java/com/capturecat/core/service/bookmark/BookmarkServiceTest.java Added service tests for normal and exceptional flows of bookmark registration, including user and image absence.
.../src/test/java/com/capturecat/core/domain/bookmark/BookmarkTest.java Added entity test for Bookmark instantiation.
.../src/test/java/com/capturecat/core/api/error/ErrorCodeControllerTest.java Added test for generating error code documentation for bookmark registration errors.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant BookmarkController
    participant BookmarkService
    participant SecurityContext
    participant UserRepository
    participant ImageRepository
    participant BookmarkRepository

    Client->>BookmarkController: POST /v1/bookmarks?imageId={id}
    BookmarkController->>BookmarkService: addBookmark(imageId)
    BookmarkService->>SecurityContext: getAuthentication()
    SecurityContext-->>BookmarkService: Authentication (principal: username)
    BookmarkService->>UserRepository: findByUsername(username)
    UserRepository-->>BookmarkService: User or throw USER_NOT_FOUND
    BookmarkService->>ImageRepository: findById(imageId)
    ImageRepository-->>BookmarkService: Image or throw IMAGE_NOT_FOUND
    BookmarkService->>BookmarkRepository: existsByUserAndImage(user, image)
    BookmarkRepository-->>BookmarkService: boolean (exists or not)
    alt Not exists
        BookmarkService->>BookmarkRepository: save(new Bookmark(user, image))
        BookmarkRepository-->>BookmarkService: saved Bookmark
    else Exists
        BookmarkService-->>BookmarkController: throw CoreException(BOOKMARK_DUPLICATION)
    end
    BookmarkService-->>BookmarkController: void
    BookmarkController-->>Client: ApiResponse (success)
Loading

Suggested reviewers

  • jaelyangChoi

Poem

In the garden of code where bookmarks now grow,
A rabbit hops swiftly, with tests in tow.
Docs sprout like carrots, neat rows in the sun,
Controllers and services—oh what fun!
With every commit, our features take flight—
Bookmarked for joy, in the soft morning light.
🥕✨
"""

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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: 4

🧹 Nitpick comments (7)
capturecat-core/src/docs/asciidoc/error-codes.adoc (1)

36-40: Remove unnecessary blank lines.

The extra blank lines before the bookmark registration section are not needed and affect documentation formatting consistency.

-
-
-
-
-
capturecat-core/src/main/java/com/capturecat/core/domain/bookmark/BookmarkRepository.java (1)

5-6: Consider adding bookmark-specific query methods.

While the basic JpaRepository provides CRUD operations, bookmark functionality typically requires additional methods. Consider adding methods like:

boolean existsByUserAndImage(User user, Image image);
List<Bookmark> findByUserId(Long userId);
void deleteByUserAndImage(User user, Image image);

This would prevent duplicate bookmarks and enable efficient bookmark retrieval.

capturecat-core/src/test/java/com/capturecat/core/domain/bookmark/BookmarkTest.java (1)

13-24: Expand test coverage for the Bookmark entity.

The current test only verifies object creation. Consider adding tests for:

  • Bookmark entity properties and relationships
  • Validation constraints if any
  • Equals/hashCode methods if implemented
  • Edge cases with null values
@Test
void 북마크_엔티티_속성_검증() {
    // given
    User user = DummyObject.newMockUser(1L);
    Image image = DummyObject.newMockImage(1L);
    
    // when
    Bookmark bookmark = new Bookmark(user, image);
    
    // then
    assertThat(bookmark.getUser()).isEqualTo(user);
    assertThat(bookmark.getImage()).isEqualTo(image);
    assertThat(bookmark.getCreatedAt()).isNotNull();
}
capturecat-core/src/main/java/com/capturecat/core/service/bookmark/BookmarkService.java (1)

33-38: Consider optimizing database queries if needed.

The service makes three separate database calls. While this is acceptable for the current use case, consider optimizing if this becomes a performance bottleneck.

For future optimization, you could:

  1. Use batch operations if multiple bookmarks are added simultaneously
  2. Consider caching frequently accessed user data
  3. Use database-level constraints instead of application-level checks for duplicate prevention
capturecat-core/src/test/java/com/capturecat/core/service/bookmark/BookmarkServiceTest.java (3)

48-67: Consider verifying the actual bookmark object passed to save().

The test correctly verifies the service behavior, but it could be more specific about the bookmark object being saved.

Consider using an ArgumentCaptor to verify the actual bookmark object:

+import org.mockito.ArgumentCaptor;

 @Test
 void 북마크를_한다() {
     // given
     var user = DummyObject.newMockUser(1L);
     var image = DummyObject.newMockImage(1L);
     setupLoggedInUser(user);

     given(userRepository.findByUsername(anyString()))
         .willReturn(Optional.of(user));
     given(imageRepository.findById(anyLong()))
         .willReturn(Optional.of(image));
     given(bookmarkRepository.save(any()))
         .willReturn(new Bookmark(user, image));

     // when
     bookmarkService.addBookmark(image.getId());

     // then
+    ArgumentCaptor<Bookmark> bookmarkCaptor = ArgumentCaptor.forClass(Bookmark.class);
+    verify(bookmarkRepository).save(bookmarkCaptor.capture());
+    Bookmark savedBookmark = bookmarkCaptor.getValue();
+    assertThat(savedBookmark.getUser()).isEqualTo(user);
+    assertThat(savedBookmark.getImage()).isEqualTo(image);
-    verify(bookmarkRepository).save(any(Bookmark.class));
 }

84-99: Fix the typo in the test method name.

There's a typo in the method name that should be corrected for consistency.

-void 북마크를_할_때_이미지가_존재하지_읺으면_실패한다() {
+void 북마크를_할_때_이미지가_존재하지_않으면_실패한다() {

The test logic itself is correct and properly handles the image not found scenario.


101-107: Security context setup is functional but could be more robust.

The helper method correctly mocks the Spring Security context, but consider using a more explicit approach to avoid potential issues with static state.

Consider using @WithMockUser annotation or cleaning up the SecurityContext after each test:

+import org.junit.jupiter.api.AfterEach;

+@AfterEach
+void clearSecurityContext() {
+    SecurityContextHolder.clearContext();
+}

 private void setupLoggedInUser(User user) {
     Authentication authentication = Mockito.mock(Authentication.class);
     SecurityContext securityContext = Mockito.mock(SecurityContext.class);
     given(securityContext.getAuthentication()).willReturn(authentication);
     SecurityContextHolder.setContext(securityContext);
     given(authentication.getPrincipal()).willReturn(new LoginUser(user));
 }

Alternatively, consider using @WithMockUser for simpler authentication setup if it fits your testing strategy.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between baae92b and a550f5e.

📒 Files selected for processing (11)
  • capturecat-core/src/docs/asciidoc/bookmark.adoc (1 hunks)
  • capturecat-core/src/docs/asciidoc/error-codes.adoc (1 hunks)
  • capturecat-core/src/docs/asciidoc/index.adoc (1 hunks)
  • capturecat-core/src/main/java/com/capturecat/core/api/bookmark/BookmarkController.java (1 hunks)
  • capturecat-core/src/main/java/com/capturecat/core/domain/bookmark/Bookmark.java (1 hunks)
  • capturecat-core/src/main/java/com/capturecat/core/domain/bookmark/BookmarkRepository.java (1 hunks)
  • capturecat-core/src/main/java/com/capturecat/core/service/bookmark/BookmarkService.java (1 hunks)
  • capturecat-core/src/test/java/com/capturecat/core/api/bookmark/BookmarkControllerTest.java (1 hunks)
  • capturecat-core/src/test/java/com/capturecat/core/api/error/ErrorCodeControllerTest.java (1 hunks)
  • capturecat-core/src/test/java/com/capturecat/core/domain/bookmark/BookmarkTest.java (1 hunks)
  • capturecat-core/src/test/java/com/capturecat/core/service/bookmark/BookmarkServiceTest.java (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
capturecat-core/src/test/java/com/capturecat/core/domain/bookmark/BookmarkTest.java (1)
capturecat-core/src/test/java/com/capturecat/core/DummyObject.java (1)
  • DummyObject (16-63)
capturecat-core/src/test/java/com/capturecat/core/api/bookmark/BookmarkControllerTest.java (1)
capturecat-tests/api-docs/src/main/java/com/capturecat/test/api/RestDocsUtil.java (1)
  • RestDocsUtil (7-22)
capturecat-core/src/test/java/com/capturecat/core/service/bookmark/BookmarkServiceTest.java (1)
capturecat-core/src/test/java/com/capturecat/core/DummyObject.java (1)
  • DummyObject (16-63)
🔇 Additional comments (7)
capturecat-core/src/docs/asciidoc/index.adoc (1)

18-18: LGTM! Documentation integration looks correct.

The bookmark documentation is properly integrated into the main documentation index with correct placement and formatting.

capturecat-core/src/docs/asciidoc/error-codes.adoc (1)

41-43: Documentation structure looks good.

The bookmark registration error codes section follows the established pattern and is properly formatted.

capturecat-core/src/test/java/com/capturecat/core/api/error/ErrorCodeControllerTest.java (1)

89-94: Error code documentation test looks correct.

The test method follows the established pattern and correctly documents the expected error codes (USER_NOT_FOUND and IMAGE_NOT_FOUND) for the bookmark registration API.

capturecat-core/src/docs/asciidoc/bookmark.adoc (1)

1-13: Documentation structure looks good.

The AsciiDoc documentation is well-organized with clear sections for success and failure scenarios. The Korean text is properly formatted and the REST Docs snippet references align with the test implementation.

capturecat-core/src/test/java/com/capturecat/core/service/bookmark/BookmarkServiceTest.java (3)

1-32: Import statements and dependencies look good.

The imports are well-organized and include all necessary testing dependencies. The use of static imports for AssertJ and Mockito improves readability.


33-46: Test class setup is properly configured.

The MockitoExtension and dependency injection setup is correct. The mocked repositories (BookmarkRepository, ImageRepository, UserRepository) cover all the dependencies needed for the BookmarkService.


69-82: Error handling test for user not found is well-implemented.

The test properly simulates the scenario where a user is not found and verifies that no bookmark is saved. The use of never() verification is appropriate.

Comment on lines +17 to +37
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Bookmark {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "image_id")
private Image image;

public Bookmark(User user, Image image) {
this.user = user;
this.image = image;
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add unique constraint to prevent duplicate bookmarks.

The entity allows multiple bookmarks for the same user-image combination, which could lead to data integrity issues and duplicate entries.

Add a unique constraint on the user-image combination:

@Entity
+@Table(uniqueConstraints = @UniqueConstraint(columnNames = {"user_id", "image_id"}))
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Bookmark {

You'll also need to add the @Table import:

+import jakarta.persistence.Table;
+import jakarta.persistence.UniqueConstraint;
📝 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
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Bookmark {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "image_id")
private Image image;
public Bookmark(User user, Image image) {
this.user = user;
this.image = image;
}
}
// add these imports
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
@Entity
@Table(uniqueConstraints = @UniqueConstraint(columnNames = {"user_id", "image_id"}))
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Bookmark {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "image_id")
private Image image;
public Bookmark(User user, Image image) {
this.user = user;
this.image = image;
}
}
🤖 Prompt for AI Agents
In
capturecat-core/src/main/java/com/capturecat/core/domain/bookmark/Bookmark.java
around lines 17 to 37, the Bookmark entity currently allows duplicate entries
for the same user and image combination. To fix this, add a unique constraint on
the user and image columns by annotating the class with @Table and specifying a
uniqueConstraints attribute that enforces uniqueness on the user_id and image_id
columns. Also, import the javax.persistence.Table class to support this
annotation.

Comment on lines +20 to +24
@PostMapping
public ApiResponse<?> addBookmark(@RequestParam Long imageId) {
bookmarkService.addBookmark(imageId);
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.

🛠️ Refactor suggestion

Add input validation for the imageId parameter.

The controller lacks validation for the imageId parameter, which could allow invalid values to be passed to the service layer.

Add validation annotations to ensure data integrity:

@PostMapping
-public ApiResponse<?> addBookmark(@RequestParam Long imageId) {
+public ApiResponse<?> addBookmark(@RequestParam @Positive Long imageId) {
	bookmarkService.addBookmark(imageId);
	return ApiResponse.success();
}

Add the validation import:

+import jakarta.validation.constraints.Positive;

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

🤖 Prompt for AI Agents
In
capturecat-core/src/main/java/com/capturecat/core/api/bookmark/BookmarkController.java
around lines 20 to 24, the addBookmark method lacks validation for the imageId
parameter. Add validation annotations such as @NotNull and @Positive to the
imageId parameter to ensure it is not null and is a positive number. Also,
include the necessary import statements for these validation annotations. This
will enforce input validation before the service layer is called.

Comment on lines +36 to +53
@Test
void 북마크를_한다() {
// given
willDoNothing().given(bookmarkService).addBookmark(anyLong());

// when & then
given().contentType(ContentType.JSON)
.accept(ContentType.JSON)
.queryParam("imageId", 1L)
.when().post("/v1/bookmarks")
.then().status(HttpStatus.OK)
.apply(document("addBookmark", requestPreprocessor(), responsePreprocessor(),
queryParameters(parameterWithName("imageId").description("즐겨찾기할 이미지 ID")),
responseFields(
fieldWithPath("result").type(JsonFieldType.STRING).description("요청 성공 여부"),
fieldWithPath("data").type(JsonFieldType.NULL).optional().ignored(),
fieldWithPath("error").type(JsonFieldType.NULL).optional().ignored())));
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error scenario tests for comprehensive coverage.

The test covers the happy path well, but consider adding tests for error scenarios to ensure proper error handling.

Add tests for error scenarios:

@Test
void 존재하지_않는_이미지_ID로_북마크_추가시_예외가_발생한다() {
    // given
    willThrow(new CoreException(ErrorType.IMAGE_NOT_FOUND))
        .given(bookmarkService).addBookmark(anyLong());

    // when & then
    given().contentType(ContentType.JSON)
        .accept(ContentType.JSON)
        .queryParam("imageId", 999L)
        .when().post("/v1/bookmarks")
        .then().status(HttpStatus.BAD_REQUEST);
}
🤖 Prompt for AI Agents
In
capturecat-core/src/test/java/com/capturecat/core/api/bookmark/BookmarkControllerTest.java
around lines 36 to 53, the current test only covers the successful bookmark
addition scenario. To improve test coverage, add a new test method that
simulates an error scenario where adding a bookmark with a non-existent image ID
throws a CoreException with ErrorType.IMAGE_NOT_FOUND. Mock the bookmarkService
to throw this exception when addBookmark is called with any long value, then
verify that the API responds with a BAD_REQUEST status. This will ensure proper
error handling is tested.

Comment on lines +28 to +40
@Transactional
public void addBookmark(Long imageId) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
LoginUser loginUser = (LoginUser)authentication.getPrincipal();

User user = userRepository.findByUsername(loginUser.getUsername())
.orElseThrow(() -> new CoreException(ErrorType.USER_NOT_FOUND));
Image image = imageRepository.findById(imageId)
.orElseThrow(() -> new CoreException(ErrorType.IMAGE_NOT_FOUND));

bookmarkRepository.save(new Bookmark(user, image));
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Address race condition and add proper validation.

The service has several critical issues that need to be addressed:

  1. Race condition: Multiple concurrent requests could create duplicate bookmarks
  2. Missing null checks: Authentication and principal could be null
  3. Unsafe casting: Principal casting without type checking

Apply these fixes:

@Transactional
public void addBookmark(Long imageId) {
	Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+	if (authentication == null || authentication.getPrincipal() == null) {
+		throw new CoreException(ErrorType.UNAUTHORIZED);
+	}
+	
+	if (!(authentication.getPrincipal() instanceof LoginUser)) {
+		throw new CoreException(ErrorType.UNAUTHORIZED);
+	}
+	
	LoginUser loginUser = (LoginUser)authentication.getPrincipal();

	User user = userRepository.findByUsername(loginUser.getUsername())
		.orElseThrow(() -> new CoreException(ErrorType.USER_NOT_FOUND));
	Image image = imageRepository.findById(imageId)
		.orElseThrow(() -> new CoreException(ErrorType.IMAGE_NOT_FOUND));

+	// Check if bookmark already exists to prevent duplicates
+	if (bookmarkRepository.existsByUserAndImage(user, image)) {
+		return; // Or throw a specific exception if duplicates should be an error
+	}

	bookmarkRepository.save(new Bookmark(user, image));
}

You'll need to add the existsByUserAndImage method to BookmarkRepository:

boolean existsByUserAndImage(User user, Image image);
🤖 Prompt for AI Agents
In
capturecat-core/src/main/java/com/capturecat/core/service/bookmark/BookmarkService.java
around lines 28 to 40, fix the race condition by checking if a bookmark already
exists using a new existsByUserAndImage method in BookmarkRepository before
saving. Add null checks for Authentication and principal to avoid
NullPointerExceptions. Also, verify the principal's type before casting to
LoginUser to prevent ClassCastException. Implement these validations and
conditional checks to ensure safe and correct bookmark creation.

# Conflicts:
#	capturecat-core/src/docs/asciidoc/error-codes.adoc
#	capturecat-core/src/test/java/com/capturecat/core/api/error/ErrorCodeControllerTest.java
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/test/java/com/capturecat/core/service/bookmark/BookmarkServiceTest.java (1)

87-87: Fix typo in Korean text.

There's a typo in the Korean text: "읺으면" should be "없으면".

-	void 즐겨찾기를_할_때_이미지가_존재하지_읺으면_실패한다() {
+	void 즐겨찾기를_할_때_이미지가_존재하지_않으면_실패한다() {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ad8e5d0 and 6ed3733.

📒 Files selected for processing (8)
  • capturecat-core/src/main/java/com/capturecat/core/domain/bookmark/BookmarkRepository.java (1 hunks)
  • capturecat-core/src/main/java/com/capturecat/core/service/bookmark/BookmarkService.java (1 hunks)
  • capturecat-core/src/main/java/com/capturecat/core/support/error/ErrorCode.java (1 hunks)
  • capturecat-core/src/main/java/com/capturecat/core/support/error/ErrorType.java (1 hunks)
  • capturecat-core/src/test/java/com/capturecat/core/api/bookmark/BookmarkControllerTest.java (1 hunks)
  • capturecat-core/src/test/java/com/capturecat/core/api/error/ErrorCodeControllerTest.java (1 hunks)
  • capturecat-core/src/test/java/com/capturecat/core/domain/bookmark/BookmarkTest.java (1 hunks)
  • capturecat-core/src/test/java/com/capturecat/core/service/bookmark/BookmarkServiceTest.java (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • capturecat-core/src/main/java/com/capturecat/core/support/error/ErrorCode.java
🚧 Files skipped from review as they are similar to previous changes (4)
  • capturecat-core/src/test/java/com/capturecat/core/domain/bookmark/BookmarkTest.java
  • capturecat-core/src/test/java/com/capturecat/core/api/bookmark/BookmarkControllerTest.java
  • capturecat-core/src/test/java/com/capturecat/core/api/error/ErrorCodeControllerTest.java
  • capturecat-core/src/main/java/com/capturecat/core/service/bookmark/BookmarkService.java
🧰 Additional context used
🧬 Code Graph Analysis (1)
capturecat-core/src/test/java/com/capturecat/core/service/bookmark/BookmarkServiceTest.java (1)
capturecat-core/src/test/java/com/capturecat/core/DummyObject.java (1)
  • DummyObject (16-63)
🔇 Additional comments (4)
capturecat-core/src/main/java/com/capturecat/core/support/error/ErrorType.java (1)

34-35: LGTM! Well-integrated error type addition.

The new BOOKMARK_DUPLICATION error type follows the established pattern with appropriate HTTP status (400 Bad Request) and log level (WARN) for duplicate resource scenarios.

capturecat-core/src/main/java/com/capturecat/core/domain/bookmark/BookmarkRepository.java (1)

8-11: LGTM! Clean repository interface following Spring Data conventions.

The BookmarkRepository interface properly extends JpaRepository and the custom method existsByUserAndImage follows Spring Data naming conventions for existence checks, which is perfect for preventing duplicate bookmarks.

capturecat-core/src/test/java/com/capturecat/core/service/bookmark/BookmarkServiceTest.java (2)

48-121: Excellent test coverage and structure.

The test suite provides comprehensive coverage of all bookmark service scenarios:

  • Successful bookmark creation
  • User not found error handling
  • Image not found error handling
  • Duplicate bookmark prevention

The tests follow good practices with proper mocking, BDD-style structure, and verification that the repository save method is called only in success scenarios.


123-129: Well-structured security context setup.

The setupLoggedInUser helper method properly mocks the Spring Security context with the LoginUser wrapper, enabling clean testing of authenticated service methods.

@leeedohyun leeedohyun merged commit 62361da into develop Jul 8, 2025
2 checks passed
@leeedohyun leeedohyun deleted the feature/#52-bookmark branch July 8, 2025 13:55
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