Skip to content

Commit b998f5f

Browse files
committed
Merge branch 'develop' into feature/#131-userSettings-deleteScreenShot
# Conflicts: # capturecat-core/src/docs/asciidoc/user.adoc
2 parents 20ee912 + 03dcfc0 commit b998f5f

File tree

15 files changed

+251
-16
lines changed

15 files changed

+251
-16
lines changed

capturecat-core/src/docs/asciidoc/bookmark.adoc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,16 @@ operation::getBookmarkImages[snippets='curl-request,query-parameters,http-respon
2121

2222
<<error-codes#즐겨찾기한-이미지-조회, 즐겨찾기한 이미지 조회 API에서 발생할 수 있는 에러>>를 살펴보세요.
2323

24+
[[즐겨찾기한-이미지의-태그-조회]]
25+
=== 즐겨찾기한 이미지의 태그 조회
26+
==== 성공
27+
operation::getBookmarkImageTags[snippets='curl-request,query-parameters,http-response,response-fields']
28+
29+
==== 실패
30+
즐겨찾기한 이미지의 태그 조회가 실패했다면 HTTP 상태 코드와 함께 <<에러-객체-형식, 에러 객체>>가 돌아옵니다.
31+
32+
<<error-codes#즐겨찾기한-이미지의-태그-조회, 즐겨찾기한 이미지의 태그 조회 API에서 발생할 수 있는 에러>>를 살펴보세요.
33+
2434
[[즐겨찾기-삭제]]
2535
=== 즐겨찾기 삭제
2636
==== 성공

capturecat-core/src/docs/asciidoc/error-codes.adoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ include::{snippets}/errorCode/addBookmark/error-codes.adoc[]
5454
=== 즐겨찾기한 이미지 조회
5555
include::{snippets}/errorCode/getBookmarkImages/error-codes.adoc[]
5656

57+
[[즐겨찾기한-이미지의-태그-조회]]
58+
=== 즐겨찾기한 이미지의 태그 조회
59+
include::{snippets}/errorCode/getBookmarkImageTags/error-codes.adoc[]
60+
5761
[[즐겨찾기-삭제]]
5862
=== 즐겨찾기 삭제
5963
include::{snippets}/errorCode/deleteBookmark/error-codes.adoc[]

capturecat-core/src/main/java/com/capturecat/core/api/bookmark/BookmarkController.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import com.capturecat.core.service.auth.LoginUser;
1616
import com.capturecat.core.service.bookmark.BookmarkService;
1717
import com.capturecat.core.service.image.ImageWithTagsResponse;
18+
import com.capturecat.core.service.image.TagResponse;
1819
import com.capturecat.core.support.response.ApiResponse;
1920
import com.capturecat.core.support.response.CursorResponse;
2021

@@ -34,8 +35,17 @@ public ApiResponse<?> addBookmark(@RequestParam Long imageId, @AuthenticationPri
3435
@GetMapping("/images")
3536
public ApiResponse<CursorResponse<ImageWithTagsResponse>> getBookmarkImages(
3637
@AuthenticationPrincipal LoginUser loginUser,
38+
@RequestParam(required = false) Long tagId,
3739
@PageableDefault Pageable pageable) {
38-
return ApiResponse.success(bookmarkService.getBookmarkImages(loginUser, pageable));
40+
return ApiResponse.success(bookmarkService.getBookmarkImages(loginUser, tagId, pageable));
41+
}
42+
43+
@GetMapping("/tags")
44+
public ApiResponse<CursorResponse<TagResponse>> getBookmarkImageTags(
45+
@AuthenticationPrincipal LoginUser loginUser,
46+
@PageableDefault Pageable pageable) {
47+
48+
return ApiResponse.success(bookmarkService.getBookmarkImageTags(loginUser, pageable));
3949
}
4050

4151
@DeleteMapping

capturecat-core/src/main/java/com/capturecat/core/domain/bookmark/BookmarkCustomRepository.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
import org.springframework.data.domain.Pageable;
44
import org.springframework.data.domain.Slice;
55

6+
import com.capturecat.core.domain.tag.Tag;
67
import com.capturecat.core.domain.user.User;
78

89
public interface BookmarkCustomRepository {
910

10-
Slice<Bookmark> searchBookmarksByUser(User user, Pageable pageable);
11+
Slice<Bookmark> searchBookmarksByUser(User user, Tag tag, Pageable pageable);
1112
}

capturecat-core/src/main/java/com/capturecat/core/domain/bookmark/BookmarkCustomRepositoryImpl.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,20 @@
22

33
import static com.capturecat.core.domain.bookmark.QBookmark.bookmark;
44
import static com.capturecat.core.domain.image.QImage.image;
5+
import static com.capturecat.core.domain.tag.QImageTag.imageTag;
56

67
import java.util.List;
78

89
import org.springframework.data.domain.Pageable;
910
import org.springframework.data.domain.Slice;
1011

12+
import com.querydsl.core.types.dsl.BooleanExpression;
1113
import com.querydsl.jpa.impl.JPAQueryFactory;
1214

1315
import lombok.RequiredArgsConstructor;
1416

17+
import com.capturecat.core.domain.tag.QTag;
18+
import com.capturecat.core.domain.tag.Tag;
1519
import com.capturecat.core.domain.user.User;
1620
import com.capturecat.core.support.util.SliceUtil;
1721

@@ -21,16 +25,25 @@ public class BookmarkCustomRepositoryImpl implements BookmarkCustomRepository {
2125
private final JPAQueryFactory queryFactory;
2226

2327
@Override
24-
public Slice<Bookmark> searchBookmarksByUser(User user, Pageable pageable) {
28+
public Slice<Bookmark> searchBookmarksByUser(User user, Tag tag, Pageable pageable) {
2529
List<Bookmark> bookmarks = queryFactory
2630
.selectFrom(bookmark)
2731
.join(bookmark.image, image).fetchJoin()
28-
.where(bookmark.user.eq(user))
32+
.leftJoin(imageTag).on(imageTag.image.eq(image))
33+
.leftJoin(QTag.tag).on(imageTag.tag.eq(QTag.tag))
34+
.where(bookmark.user.eq(user), eqTag(tag))
2935
.offset(pageable.getOffset())
3036
.limit(pageable.getPageSize() + 1)
3137
.orderBy(bookmark.id.desc())
3238
.fetch();
3339

3440
return SliceUtil.toSlice(bookmarks, pageable);
3541
}
42+
43+
private BooleanExpression eqTag(Tag tag) {
44+
if (tag == null) {
45+
return null;
46+
}
47+
return imageTag.tag.eq(tag);
48+
}
3649
}

capturecat-core/src/main/java/com/capturecat/core/domain/tag/TagCustomRepository.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,6 @@ public interface TagCustomRepository {
1616
Slice<Tag> searchMostUsedTagsByUser(User user, Pageable pageable);
1717

1818
List<Tag> searchByKeyword(String keyword, Long userId, int size);
19+
20+
Slice<Tag> searchTagsByMemberBookmark(User user, Pageable pageable);
1921
}

capturecat-core/src/main/java/com/capturecat/core/domain/tag/TagCustomRepositoryImpl.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.capturecat.core.domain.tag;
22

3+
import static com.capturecat.core.domain.bookmark.QBookmark.bookmark;
34
import static com.capturecat.core.domain.image.QImage.image;
45
import static com.capturecat.core.domain.tag.QImageTag.imageTag;
56
import static com.capturecat.core.domain.tag.QTag.tag;
@@ -13,6 +14,7 @@
1314

1415
import lombok.RequiredArgsConstructor;
1516

17+
import com.capturecat.core.domain.user.QUser;
1618
import com.capturecat.core.domain.user.User;
1719
import com.capturecat.core.support.querydsl.CommonTagQueryConditions;
1820
import com.capturecat.core.support.util.SliceUtil;
@@ -87,4 +89,22 @@ public List<Tag> searchByKeyword(String keyword, Long userId, int size) {
8789
.limit(size)
8890
.fetch();
8991
}
92+
93+
@Override
94+
public Slice<Tag> searchTagsByMemberBookmark(User user, Pageable pageable) {
95+
List<Tag> content = queryFactory
96+
.select(tag)
97+
.from(imageTag)
98+
.join(imageTag.tag, tag)
99+
.join(imageTag.image, image)
100+
.join(bookmark).on(bookmark.image.eq(image))
101+
.join(bookmark.user, QUser.user)
102+
.where(QUser.user.eq(user))
103+
.offset(pageable.getOffset())
104+
.limit(pageable.getPageSize() + 1)
105+
.orderBy(imageTag.createdDate.desc())
106+
.fetch();
107+
108+
return SliceUtil.toSlice(content, pageable);
109+
}
90110
}

capturecat-core/src/main/java/com/capturecat/core/domain/user/UserRepository.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,4 @@ public interface UserRepository extends JpaRepository<User, Long> {
88
Optional<User> findByUsername(String username);
99

1010
boolean existsByUsername(String username);
11-
12-
Optional<User> findByEmail(String email);
1311
}

capturecat-core/src/main/java/com/capturecat/core/service/bookmark/BookmarkService.java

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,13 @@
1313
import com.capturecat.core.domain.bookmark.BookmarkRepository;
1414
import com.capturecat.core.domain.image.Image;
1515
import com.capturecat.core.domain.image.ImageRepository;
16+
import com.capturecat.core.domain.tag.Tag;
17+
import com.capturecat.core.domain.tag.TagRepository;
1618
import com.capturecat.core.domain.user.User;
1719
import com.capturecat.core.domain.user.UserRepository;
1820
import com.capturecat.core.service.auth.LoginUser;
1921
import com.capturecat.core.service.image.ImageWithTagsResponse;
22+
import com.capturecat.core.service.image.TagResponse;
2023
import com.capturecat.core.support.error.CoreException;
2124
import com.capturecat.core.support.error.ErrorType;
2225
import com.capturecat.core.support.response.CursorResponse;
@@ -29,6 +32,7 @@ public class BookmarkService {
2932
private final BookmarkRepository bookmarkRepository;
3033
private final ImageRepository imageRepository;
3134
private final UserRepository userRepository;
35+
private final TagRepository tagRepository;
3236

3337
@Transactional
3438
public void addBookmark(Long imageId, LoginUser loginUser) {
@@ -43,10 +47,11 @@ public void addBookmark(Long imageId, LoginUser loginUser) {
4347
}
4448

4549
@Transactional(readOnly = true)
46-
public CursorResponse<ImageWithTagsResponse> getBookmarkImages(LoginUser loginUser, Pageable pageable) {
50+
public CursorResponse<ImageWithTagsResponse> getBookmarkImages(LoginUser loginUser, Long tagId, Pageable pageable) {
4751
User user = userRepository.findByUsername(loginUser.getUsername())
4852
.orElseThrow(() -> new CoreException(ErrorType.USER_NOT_FOUND));
49-
Slice<Bookmark> bookmarks = bookmarkRepository.searchBookmarksByUser(user, pageable);
53+
Tag tag = getTagOrNull(tagId);
54+
Slice<Bookmark> bookmarks = bookmarkRepository.searchBookmarksByUser(user, tag, pageable);
5055

5156
List<ImageWithTagsResponse> responses = bookmarks.getContent().stream()
5257
.map(Bookmark::getImage)
@@ -56,6 +61,19 @@ public CursorResponse<ImageWithTagsResponse> getBookmarkImages(LoginUser loginUs
5661
return CursorUtil.toCursorResponse(responses, bookmarks.hasNext(), ImageWithTagsResponse::id);
5762
}
5863

64+
@Transactional(readOnly = true)
65+
public CursorResponse<TagResponse> getBookmarkImageTags(LoginUser loginUser, Pageable pageable) {
66+
User user = userRepository.findByUsername(loginUser.getUsername())
67+
.orElseThrow(() -> new CoreException(ErrorType.USER_NOT_FOUND));
68+
Slice<Tag> tags = tagRepository.searchTagsByMemberBookmark(user, pageable);
69+
70+
List<TagResponse> responses = tags.getContent().stream()
71+
.map(TagResponse::from)
72+
.toList();
73+
74+
return CursorUtil.toCursorResponse(responses, tags.hasNext(), TagResponse::id);
75+
}
76+
5977
@Transactional
6078
public void deleteBookmark(Long imageId, LoginUser loginUser) {
6179
User user = userRepository.findByUsername(loginUser.getUsername())
@@ -73,4 +91,12 @@ private void validateBookmarkDuplication(User user, Image image) {
7391
throw new CoreException(ErrorType.BOOKMARK_DUPLICATION);
7492
}
7593
}
94+
95+
private Tag getTagOrNull(Long tagId) {
96+
if (tagId == null) {
97+
return null;
98+
}
99+
return tagRepository.findById(tagId)
100+
.orElseThrow(() -> new CoreException(ErrorType.TAG_NOT_FOUND));
101+
}
76102
}

capturecat-core/src/test/java/com/capturecat/core/api/bookmark/BookmarkControllerTest.java

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import com.capturecat.core.DummyObject;
2626
import com.capturecat.core.service.bookmark.BookmarkService;
2727
import com.capturecat.core.service.image.ImageWithTagsResponse;
28+
import com.capturecat.core.service.image.TagResponse;
2829
import com.capturecat.core.support.util.CursorUtil;
2930
import com.capturecat.test.api.RestDocsTest;
3031

@@ -62,7 +63,7 @@ void setUp() {
6263
@Test
6364
void 즐겨찾기한_이미지를_조회한다() {
6465
// given
65-
BDDMockito.given(bookmarkService.getBookmarkImages(any(), any())).willReturn(
66+
BDDMockito.given(bookmarkService.getBookmarkImages(any(), any(), any())).willReturn(
6667
CursorUtil.toCursorResponse(
6768
List.of(ImageWithTagsResponse.from(DummyObject.newMockImage(1L))),
6869
false,
@@ -78,7 +79,9 @@ void setUp() {
7879
.apply(document("getBookmarkImages", requestPreprocessor(), responsePreprocessor(),
7980
queryParameters(
8081
parameterWithName("page").description("페이지 번호 (0부터 시작)").optional(),
81-
parameterWithName("size").description("페이지 크기 (기본값: 10, 최대: 100)").optional()),
82+
parameterWithName("size").description("페이지 크기 (기본값: 10, 최대: 100)").optional(),
83+
parameterWithName("tagId").description("태그 ID (선택한 태그로 필터링)").optional()
84+
),
8285
responseFields(
8386
fieldWithPath("result").type(JsonFieldType.STRING).description("요청 결과"),
8487
fieldWithPath("data.hasNext").type(JsonFieldType.BOOLEAN).description("다음 페이지 여부"),
@@ -92,6 +95,35 @@ void setUp() {
9295
fieldWithPath("error").type(JsonFieldType.NULL).optional().ignored())));
9396
}
9497

98+
@Test
99+
void 즐겨찾기한_이미지의_이미지태그를_조회한다() {
100+
// given
101+
BDDMockito.given(bookmarkService.getBookmarkImageTags(any(), any())).willReturn(
102+
CursorUtil.toCursorResponse(List.of(new TagResponse(1L, "고양이"), new TagResponse(2L, "cat")),
103+
false,
104+
TagResponse::id));
105+
106+
// when & then
107+
given().contentType(ContentType.JSON)
108+
.accept(ContentType.JSON)
109+
.queryParam("page", 0)
110+
.queryParam("size", 10)
111+
.when().get("/v1/bookmarks/tags")
112+
.then().status(HttpStatus.OK).log().all()
113+
.apply(document("getBookmarkImageTags", requestPreprocessor(), responsePreprocessor(),
114+
queryParameters(
115+
parameterWithName("page").description("페이지 번호 (0부터 시작)").optional(),
116+
parameterWithName("size").description("페이지 크기 (기본값: 10, 최대: 100)").optional()),
117+
responseFields(
118+
fieldWithPath("result").type(JsonFieldType.STRING).description("요청 결과"),
119+
fieldWithPath("data.hasNext").type(JsonFieldType.BOOLEAN).description("다음 페이지 여부"),
120+
fieldWithPath("data.lastCursor").type(JsonFieldType.NUMBER).description("마지막 커서 ID"),
121+
fieldWithPath("data.items").type(JsonFieldType.ARRAY).description("즐겨찾기한 이미지 태그 목록"),
122+
fieldWithPath("data.items[].id").type(JsonFieldType.NUMBER).description("이미지 태그 ID"),
123+
fieldWithPath("data.items[].name").type(JsonFieldType.STRING).description("이미지 태그 이름"),
124+
fieldWithPath("error").type(JsonFieldType.NULL).optional().ignored())));
125+
}
126+
95127
@Test
96128
void 즐겨찾기에서_삭제한다() {
97129
// given

0 commit comments

Comments
 (0)