Skip to content

Commit 0f1950e

Browse files
committed
⚡ MarkSphere v1.0.6
북마크 로딩 방식 변경(커서 기반) QR코드 URL 수정 댓글, 북마크 등록 시 DB에 생성된 정보 반환 북마크 로딩 시 최신순으로 로딩
1 parent e71eb2d commit 0f1950e

File tree

12 files changed

+216
-122
lines changed

12 files changed

+216
-122
lines changed

src/main/java/com/sonkim/bookmarking/domain/bookmark/controller/TeamBookmarkController.java

Lines changed: 20 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@
22

33
import com.sonkim.bookmarking.auth.entity.UserDetailsImpl;
44
import com.sonkim.bookmarking.common.aop.Idempotent;
5-
import com.sonkim.bookmarking.common.dto.PageResponseDto;
5+
import com.sonkim.bookmarking.common.dto.CursorResultDto;
66
import com.sonkim.bookmarking.domain.bookmark.dto.BookmarkRequestDto;
77
import com.sonkim.bookmarking.domain.bookmark.dto.BookmarkResponseDto;
88
import com.sonkim.bookmarking.domain.bookmark.dto.BookmarkSearchCond;
9-
import com.sonkim.bookmarking.domain.bookmark.entity.Bookmark;
109
import com.sonkim.bookmarking.domain.bookmark.service.BookmarkService;
1110
import io.swagger.v3.oas.annotations.Parameter;
1211
import io.swagger.v3.oas.annotations.Operation;
@@ -15,16 +14,12 @@
1514
import io.swagger.v3.oas.annotations.tags.Tag;
1615
import lombok.RequiredArgsConstructor;
1716
import lombok.extern.slf4j.Slf4j;
18-
import org.springframework.data.domain.Pageable;
19-
import org.springframework.data.web.PageableDefault;
2017
import org.springframework.http.HttpStatus;
2118
import org.springframework.http.ResponseEntity;
2219
import org.springframework.security.core.annotation.AuthenticationPrincipal;
2320
import org.springframework.stereotype.Controller;
2421
import org.springframework.web.bind.annotation.*;
2522

26-
import java.util.Map;
27-
2823
@Tag(name = "그룹 북마크 관리", description = "그룹 내 북마크 생성 및 조회 API")
2924
@Slf4j
3025
@RequiredArgsConstructor
@@ -42,58 +37,58 @@ public class TeamBookmarkController {
4237
})
4338
@PostMapping("/{groupId}/bookmarks")
4439
@Idempotent
45-
public ResponseEntity<Map<String, Object>> createBookmark(@AuthenticationPrincipal UserDetailsImpl userDetails,
40+
public ResponseEntity<BookmarkResponseDto> createBookmark(@AuthenticationPrincipal UserDetailsImpl userDetails,
4641
@PathVariable Long groupId,
4742
@RequestBody BookmarkRequestDto bookmarkRequestDto) {
4843
log.info("userId: {}, url: {} 북마크 생성 요청", userDetails.getId(), bookmarkRequestDto.getUrl());
49-
Bookmark bookmark = bookmarkService.createBookmark(userDetails.getId(), groupId, bookmarkRequestDto);
50-
51-
Map<String, Object> response = Map.of(
52-
"message", "Bookmark created successfully",
53-
"bookmarkId", bookmark.getId()
54-
);
44+
BookmarkResponseDto responseDto = bookmarkService.createBookmark(userDetails.getId(), groupId, bookmarkRequestDto);
5545

56-
return ResponseEntity.status(HttpStatus.CREATED).body(response);
46+
return ResponseEntity.status(HttpStatus.CREATED).body(responseDto);
5747
}
5848

59-
@Operation(summary = "그룹 내 모든 북마크 조회 (페이징)",
49+
@Operation(summary = "그룹 내 모든 북마크 조회 (커서 페이징)",
6050
description = "특정 그룹에 속한 모든 북마크를 페이징하여 조회합니다.",
6151
parameters = {
62-
@Parameter(name = "page", description = "표시할 페이지 (1부터 시작)")
52+
@Parameter(name = "cursor", description = "다음 페이지를 위한 커서 ID(마지막으로 조회한 북마크 ID, 첫 페이지는 null)"),
53+
@Parameter(name = "size", description = "한 페이지당 항목 수")
6354
})
6455
@ApiResponse(responseCode = "200", description = "북마크 목록 조회 성공")
6556
@GetMapping("/{groupId}/bookmarks")
66-
public ResponseEntity<PageResponseDto<BookmarkResponseDto>> getBookmarksOfGroup(
57+
public ResponseEntity<CursorResultDto<BookmarkResponseDto>> getBookmarksOfGroup(
6758
@AuthenticationPrincipal UserDetailsImpl userDetails,
6859
@PathVariable("groupId") Long groupId,
6960
@RequestParam(required = false) String keyword,
7061
@RequestParam(required = false) Long tagId,
7162
@RequestParam(required = false) Long categoryId,
72-
@Parameter(hidden = true) @PageableDefault(sort = "createdAt") Pageable pageable) {
63+
@RequestParam(required = false) Long cursor,
64+
@RequestParam(required = false, defaultValue = "10") int size
65+
) {
7366
// 검색 조건 DTO 생성
7467
BookmarkSearchCond cond = BookmarkSearchCond.builder()
7568
.keyword(keyword)
7669
.tagId(tagId)
7770
.categoryId(categoryId)
7871
.build();
7972

80-
PageResponseDto<BookmarkResponseDto> bookmarkPage = bookmarkService.searchBookmarks(userDetails.getId(), groupId, cond, pageable);
73+
CursorResultDto<BookmarkResponseDto> bookmarkPage = bookmarkService.searchBookmarks(userDetails.getId(), groupId, cond, cursor, size);
8174
return ResponseEntity.ok(bookmarkPage);
8275
}
8376

84-
@Operation(summary = "지도 표시용 북마크 목록 조회 (필터링, 페이징)",
77+
@Operation(summary = "지도 표시용 북마크 목록 조회 (필터링, 커서 페이징)",
8578
description = "특정 그룹에서 위치 정보가 있는 북마크를 조건(카테고리, 키워드, 태그)에 따라 필터링하여 조회합니다.",
8679
parameters = {
87-
@Parameter(name = "page", description = "표시할 페이지 (1부터 시작)")
80+
@Parameter(name = "cursor", description = "다음 페이지를 위한 커서 ID(마지막으로 조회한 북마크 ID, 첫 페이지는 null)"),
81+
@Parameter(name = "size", description = "한 페이지당 항목 수")
8882
})
8983
@GetMapping("/{groupId}/bookmarks/map")
90-
public ResponseEntity<PageResponseDto<BookmarkResponseDto>> getBookmarksForMap(
84+
public ResponseEntity<CursorResultDto<BookmarkResponseDto>> getBookmarksForMap(
9185
@AuthenticationPrincipal UserDetailsImpl userDetails,
9286
@Parameter(description = "북마크를 조회할 그룹 ID") @PathVariable("groupId") Long groupId,
87+
@RequestParam(required = false) String keyword,
9388
@RequestParam(required = false) Long categoryId,
9489
@RequestParam(required = false) Long tagId,
95-
@RequestParam(required = false) String keyword,
96-
@Parameter(hidden = true) @PageableDefault(size = 10) Pageable pageable
90+
@RequestParam(required = false) Long cursor,
91+
@RequestParam(required = false, defaultValue = "10") int size
9792
) {
9893
// 검색 조건 DTO 생성
9994
BookmarkSearchCond cond = BookmarkSearchCond.builder()
@@ -103,7 +98,7 @@ public ResponseEntity<PageResponseDto<BookmarkResponseDto>> getBookmarksForMap(
10398
.forMap(true)
10499
.build();
105100

106-
PageResponseDto<BookmarkResponseDto> bookmarks = bookmarkService.searchBookmarks(userDetails.getId(), groupId, cond, pageable);
101+
CursorResultDto<BookmarkResponseDto> bookmarks = bookmarkService.searchBookmarks(userDetails.getId(), groupId, cond, cursor, size);
107102
return ResponseEntity.ok(bookmarks);
108103
}
109104
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.sonkim.bookmarking.domain.bookmark.dto;
2+
3+
import com.sonkim.bookmarking.domain.bookmark.entity.Bookmark;
4+
import lombok.AllArgsConstructor;
5+
import lombok.Data;
6+
7+
@Data
8+
@AllArgsConstructor
9+
public class LikedBookmarkWrapper {
10+
private Bookmark bookmark;
11+
private Long bookmarkLikeId;
12+
}

src/main/java/com/sonkim/bookmarking/domain/bookmark/entity/BookmarkLike.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,4 @@ public class BookmarkLike {
2525
@ManyToOne(fetch = FetchType.LAZY)
2626
@JoinColumn(name = "bookmark_id", nullable = false)
2727
private Bookmark bookmark;
28-
2928
}
Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
package com.sonkim.bookmarking.domain.bookmark.repository;
22

33
import com.sonkim.bookmarking.domain.bookmark.dto.BookmarkSearchCond;
4+
import com.sonkim.bookmarking.domain.bookmark.dto.LikedBookmarkWrapper;
45
import com.sonkim.bookmarking.domain.bookmark.entity.Bookmark;
5-
import org.springframework.data.domain.Page;
6-
import org.springframework.data.domain.Pageable;
76
import org.springframework.data.repository.query.Param;
87

98
import java.util.Optional;
9+
import java.util.List;
1010

1111
public interface BookmarkRepositoryCustom {
12-
// 검색 조건을 받아와 검색 처리
13-
Page<Bookmark> search(Long teamId, BookmarkSearchCond cond, Pageable pageable);
12+
// 검색 조건을 받아와 x검색 처리
13+
List<Bookmark> search(Long teamId, BookmarkSearchCond cond, Long cursorId, int size);
1414

1515
// 북마크 조회 시 북마크-태그와 태그 정보 모두 가져오는 쿼리
1616
Optional<Bookmark> findByIdWithTags(@Param("id") Long id);
@@ -19,5 +19,8 @@ public interface BookmarkRepositoryCustom {
1919
void bulkSetCategoryToNull(@Param("categoryId") Long categoryId);
2020

2121
// 특정 사용자가 '좋아요'를 누른 북마크 정보 조회
22-
Page<Bookmark> findLikedBookmarksByUser_Id(Long userId, Pageable pageable);
22+
List<LikedBookmarkWrapper> findLikedBookmarksByUser_Id(Long userId, Long cursorId, int size);
23+
24+
// 특정 사용자가 작성한 북마크 정보 조회
25+
List<Bookmark> findMyBookmarksByUser_Id(Long userId, Long cursorId, int size);
2326
}

src/main/java/com/sonkim/bookmarking/domain/bookmark/repository/BookmarkRepositoryCustomImpl.java

Lines changed: 42 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
package com.sonkim.bookmarking.domain.bookmark.repository;
22

3+
import com.querydsl.core.types.Projections;
4+
import com.sonkim.bookmarking.domain.bookmark.dto.LikedBookmarkWrapper;
35
import jakarta.persistence.EntityManager;
46
import com.querydsl.core.types.dsl.BooleanExpression;
5-
import com.querydsl.jpa.impl.JPAQuery;
67
import com.querydsl.jpa.impl.JPAQueryFactory;
78
import com.sonkim.bookmarking.domain.bookmark.dto.BookmarkSearchCond;
89
import com.sonkim.bookmarking.domain.bookmark.entity.Bookmark;
910
import com.sonkim.bookmarking.domain.category.entity.Category;
1011
import lombok.RequiredArgsConstructor;
11-
import org.springframework.data.domain.Page;
12-
import org.springframework.data.domain.Pageable;
13-
import org.springframework.data.support.PageableExecutionUtils;
1412

1513
import java.util.List;
1614
import java.util.Optional;
@@ -27,7 +25,7 @@ public class BookmarkRepositoryCustomImpl implements BookmarkRepositoryCustom {
2725
private final EntityManager em;
2826

2927
@Override
30-
public Page<Bookmark> search(Long teamId, BookmarkSearchCond cond, Pageable pageable) {
28+
public List<Bookmark> search(Long teamId, BookmarkSearchCond cond, Long cursorId, int size) {
3129
List<Bookmark> content = queryFactory
3230
.select(bookmark)
3331
.from(bookmark)
@@ -37,26 +35,15 @@ public Page<Bookmark> search(Long teamId, BookmarkSearchCond cond, Pageable page
3735
keywordContain(cond.getKeyword()),
3836
tagIdEq(cond.getTagId()),
3937
categoryIdEq(cond.getCategoryId()),
40-
locationIsNotNull(cond.getForMap())
38+
locationIsNotNull(cond.getForMap()),
39+
cursorIdLessThen(cursorId)
4140
)
4241
.distinct()
43-
.offset(pageable.getOffset())
44-
.limit(pageable.getPageSize())
42+
.limit(size)
43+
.orderBy(bookmark.id.desc())
4544
.fetch();
4645

47-
JPAQuery<Long> countQuery = queryFactory
48-
.select(bookmark.countDistinct())
49-
.from(bookmark)
50-
.leftJoin(bookmark.bookmarkTags, bookmarkTag)
51-
.where(
52-
bookmark.team.id.eq(teamId),
53-
keywordContain(cond.getKeyword()),
54-
tagIdEq(cond.getCategoryId()),
55-
categoryIdEq(cond.getCategoryId()),
56-
locationIsNotNull(cond.getForMap())
57-
);
58-
59-
return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne);
46+
return content;
6047
}
6148

6249
@Override
@@ -85,23 +72,34 @@ public void bulkSetCategoryToNull(Long categoryId) {
8572
}
8673

8774
@Override
88-
public Page<Bookmark> findLikedBookmarksByUser_Id(Long userId, Pageable pageable) {
89-
List<Bookmark> content = queryFactory
90-
.select(bookmark)
91-
.from(bookmark)
92-
.join(bookmark.bookmarkLikes, bookmarkLike)
93-
.where(bookmarkLike.user.id.eq(userId))
94-
.offset(pageable.getOffset())
95-
.limit(pageable.getPageSize())
75+
public List<Bookmark> findMyBookmarksByUser_Id(Long userId, Long cursorId, int size) {
76+
return queryFactory
77+
.selectFrom(bookmark)
78+
.where(
79+
bookmark.user.id.eq(userId),
80+
cursorIdLessThen(cursorId)
81+
)
82+
.orderBy(bookmark.id.desc())
83+
.limit(size)
9684
.fetch();
85+
}
9786

98-
JPAQuery<Long> countQuery = queryFactory
99-
.select(bookmark.count())
100-
.from(bookmark)
101-
.join(bookmark.bookmarkLikes, bookmarkLike)
102-
.where(bookmarkLike.user.id.eq(userId));
103-
104-
return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne);
87+
@Override
88+
public List<LikedBookmarkWrapper> findLikedBookmarksByUser_Id(Long userId, Long cursorId, int size) {
89+
return queryFactory
90+
.select(Projections.constructor(LikedBookmarkWrapper.class,
91+
bookmark,
92+
bookmarkLike.id
93+
))
94+
.from(bookmarkLike)
95+
.join(bookmarkLike.bookmark, bookmark)
96+
.where(
97+
bookmarkLike.user.id.eq(userId),
98+
bookmarkLikedCursorIdLessThen(cursorId)
99+
)
100+
.orderBy(bookmarkLike.id.desc())
101+
.limit(size)
102+
.fetch();
105103
}
106104

107105
private BooleanExpression locationIsNotNull(Boolean forMap) {
@@ -120,4 +118,12 @@ private BooleanExpression categoryIdEq(Long categoryId) {
120118
private BooleanExpression tagIdEq(Long tagId) {
121119
return tagId != null ? bookmarkTag.tag.id.eq(tagId) : null;
122120
}
121+
122+
private BooleanExpression cursorIdLessThen(Long cursorId) {
123+
return cursorId != null ? bookmark.id.lt(cursorId) : null;
124+
}
125+
126+
private BooleanExpression bookmarkLikedCursorIdLessThen(Long cursorId) {
127+
return cursorId != null ? bookmarkLike.id.lt(cursorId) : null;
128+
}
123129
}

0 commit comments

Comments
 (0)