Skip to content

Commit 6bd1b63

Browse files
committed
Refactor : 북마크 C,R,D API 리팩토링 및 OPEN API DB에 저장 로직 구현
1 parent 2ba0e2d commit 6bd1b63

8 files changed

Lines changed: 214 additions & 127 deletions

File tree

tour/src/main/java/com/jocketdan/tour/controller/BookmarkController.java

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

33
import com.jocketdan.tour.dto.BookmarkCountDTO;
44
import com.jocketdan.tour.dto.PaginatedResponseDTO;
5-
import com.jocketdan.tour.dto.ResponsePlaceDTO;
5+
import com.jocketdan.tour.dto.ResponseBookmarkDTO;
66
import com.jocketdan.tour.entity.BookmarkType;
77
import com.jocketdan.tour.service.BookmarkService;
88
import io.swagger.v3.oas.annotations.Operation;
@@ -32,38 +32,24 @@ public class BookmarkController {
3232
@Operation(
3333
summary = "북마크 추가",
3434
description = """
35-
특정 관광지를 북마크에 추가합니다.
36-
37-
### 파라미터 설명
38-
- **placeId**: OPEN API의 contentId (예: "126508", "264636")
39-
- **contentTypeId**: 콘텐츠 타입 ID
40-
- 12: 관광지
41-
- 14: 문화시설
42-
- 15: 축제 (자동으로 FESTIVAL 타입으로 분류)
43-
- 28: 레포츠
44-
- 32: 숙박
45-
- 38: 쇼핑
46-
- 39: 음식점
35+
특정 관광지를 북마크에 추가 시 DB에 OPEN API 정보들을 같이 저장합니다.
4736
4837
### 예시
4938
```
50-
POST /tour/bookmarks?placeId=126508&contentTypeId=12
39+
POST /tour/bookmarks?contentId=126508
5140
```
5241
""",
5342
tags = {"북마크 관리"}
5443
)
5544
@PostMapping("/bookmarks")
5645
public ResponseEntity<Void> addBookmark(
5746
@Parameter(description = "OPEN API contentId", required = true)
58-
@RequestParam String placeId,
59-
60-
@Parameter(description = "콘텐츠 타입 ID (12=관광지, 15=축제 등)", required = true)
61-
@RequestParam Integer contentTypeId,
47+
@RequestParam String contentId,
6248

6349
Authentication authentication
6450
) {
6551
String userEmail = authentication.getName();
66-
bookmarkService.addBookmark(placeId, contentTypeId, userEmail);
52+
bookmarkService.addBookmark(contentId, userEmail);
6753
return ResponseEntity.status(HttpStatus.CREATED).build();
6854
}
6955

@@ -74,34 +60,33 @@ public ResponseEntity<Void> addBookmark(
7460
여러 개를 동시에 삭제할 수 있습니다.
7561
7662
### 예시
77-
- 단일 삭제: `DELETE /tour/bookmarks?placeIds=126508`
78-
- 다중 삭제: `DELETE /tour/bookmarks?placeIds=126508,264636,125406`
63+
- 단일 삭제: `DELETE /tour/bookmarks?contentIds=126508`
64+
- 다중 삭제: `DELETE /tour/bookmarks?contentIds=126508,264636,125406`
7965
""",
8066
tags = {"북마크 관리"}
8167
)
8268
@DeleteMapping("/bookmarks")
8369
public ResponseEntity<Void> removeBookmark(
8470
@Parameter(
85-
name = "placeIds",
86-
description = "삭제할 placeId (쉼표로 구분하여 여러 개 가능)",
71+
name = "contentIds",
72+
description = "삭제할 contentId (쉼표로 구분하여 여러 개 가능)",
8773
required = true,
88-
example = "126508,264636",
8974
schema = @Schema(type = "string")
9075
)
91-
@RequestParam List<String> placeIds,
76+
@RequestParam List<String> contentIds,
9277

9378
Authentication authentication
9479
) {
9580
String userEmail = authentication.getName();
96-
bookmarkService.removeBookmark(placeIds, userEmail);
81+
bookmarkService.removeBookmark(contentIds, userEmail);
9782
return ResponseEntity.noContent().build();
9883
}
9984

10085
@Operation(
10186
summary = "내 북마크 목록 조회",
10287
description = """
10388
현재 로그인한 사용자의 북마크 목록을 조회합니다.
104-
OPEN API에서 실시간으로 장소 정보를 가져와 함께 반환합니다.
89+
DB에 저장한 OPEN API에서 장소 정보를 가져와 함께 반환합니다.
10590
10691
### 타입별 조회
10792
- **type=TOUR**: 일반 관광지 북마크만 조회
@@ -112,7 +97,7 @@ public ResponseEntity<Void> removeBookmark(
11297
### 정렬 (arrange)
11398
- **T**: 제목 오름차순
11499
- **C**: 최신 북마크 순 (기본값)
115-
- **R**: 평균 별점 높은 순
100+
`- **R**: 평균 별점 높은 순 (추후 구현 예정 / 아직 구현 안됐습니다)`
116101
117102
### 예시
118103
```
@@ -122,11 +107,8 @@ public ResponseEntity<Void> removeBookmark(
122107
tags = {"북마크 관리"}
123108
)
124109
@GetMapping("/bookmarks/my")
125-
public ResponseEntity<PaginatedResponseDTO<ResponsePlaceDTO>> getMyBookmarks(
126-
@Parameter(description = "페이지당 항목 수", example = "10")
110+
public ResponseEntity<PaginatedResponseDTO<ResponseBookmarkDTO>> getMyBookmarks(
127111
@RequestParam(defaultValue = "10") int numOfRows,
128-
129-
@Parameter(description = "페이지 번호 (1부터 시작)", example = "1")
130112
@RequestParam(defaultValue = "1") int pageNo,
131113

132114
@Parameter(description = "정렬 기준 (T=제목, C=최신, R=별점)", example = "C")
@@ -141,7 +123,7 @@ public ResponseEntity<PaginatedResponseDTO<ResponsePlaceDTO>> getMyBookmarks(
141123
Pageable pageable = PageRequest.of(pageNo - 1, numOfRows, sort);
142124

143125
String userEmail = authentication.getName();
144-
Page<ResponsePlaceDTO> myBookmarks = bookmarkService.getMyBookmarks(userEmail, type, pageable);
126+
Page<ResponseBookmarkDTO> myBookmarks = bookmarkService.getMyBookmarks(userEmail, type, pageable);
145127

146128
return ResponseEntity.ok(PaginatedResponseDTO.of(myBookmarks));
147129
}
@@ -169,48 +151,9 @@ public ResponseEntity<BookmarkCountDTO> getMyBookmarkCount(
169151
return ResponseEntity.ok(new BookmarkCountDTO(type, count));
170152
}
171153

172-
@Operation(
173-
summary = "내가 북마크한 placeId 목록 조회",
174-
description = """
175-
사용자가 북마크한 모든 placeId를 반환합니다.
176-
프론트엔드에서 북마크 아이콘 표시 여부를 판단할 때 사용합니다.
177-
178-
### 응답 예시
179-
```json
180-
["126508", "264636", "125406"]
181-
```
182-
183-
### 사용 예시
184-
```javascript
185-
// 프론트엔드에서 활용
186-
const bookmarkedIds = await fetchMyBookmarkedIds();
187-
const isBookmarked = bookmarkedIds.includes(placeId);
188-
```
189-
""",
190-
tags = {"북마크 관리"}
191-
)
192-
@GetMapping("/bookmarks/my/ids")
193-
public ResponseEntity<List<String>> getMyBookmarkedPlaceIds(
194-
@Parameter(description = "북마크 타입 (선택사항)")
195-
@RequestParam(required = false) BookmarkType type,
196-
197-
Authentication authentication
198-
) {
199-
String userEmail = authentication.getName();
200-
201-
List<String> placeIds;
202-
if (type != null) {
203-
placeIds = bookmarkService.getMyBookmarkedPlaceIdsByType(userEmail, type);
204-
} else {
205-
placeIds = bookmarkService.getMyBookmarkedPlaceIds(userEmail);
206-
}
207-
208-
return ResponseEntity.ok(placeIds);
209-
}
210-
211154
private Sort createBookmarkSort(String arrange) {
212155
return switch (arrange) {
213-
case "T" -> Sort.by(Sort.Direction.ASC, "place.title"); // 제목 오름차순
156+
case "T" -> Sort.by(Sort.Direction.ASC, "contentId.title"); // 제목 오름차순
214157
case "C" -> Sort.by(Sort.Direction.DESC, "createdAt"); // 최신 북마크 순
215158
case "R" -> Sort.by(Sort.Direction.DESC, "place.averageRating") // 별점 높은 순
216159
.and(Sort.by(Sort.Direction.DESC, "createdAt"));
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.jocketdan.tour.dto;
2+
3+
import com.jocketdan.tour.entity.OpenApiPlaceDetail;
4+
import lombok.AllArgsConstructor;
5+
import lombok.Getter;
6+
7+
@Getter
8+
@AllArgsConstructor
9+
public class ResponseBookmarkDTO {
10+
11+
private String contentId;
12+
private String title;
13+
private String overview;
14+
private String addr1;
15+
private String addr2;
16+
private Double mapx;
17+
private Double mapy;
18+
private String firstImage;
19+
private String firstImage2;
20+
private String homepage;
21+
private String tel;
22+
23+
public static ResponseBookmarkDTO from(OpenApiPlaceDetail entity) {
24+
return new ResponseBookmarkDTO(
25+
entity.getContentId(),
26+
entity.getTitle(),
27+
entity.getOverview(),
28+
entity.getAddr1(),
29+
entity.getAddr2(),
30+
entity.getMapx(),
31+
entity.getMapy(),
32+
entity.getFirstImage(),
33+
entity.getFirstImage2(),
34+
entity.getHomepage(),
35+
entity.getTel()
36+
);
37+
}
38+
}

tour/src/main/java/com/jocketdan/tour/entity/Bookmark.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,16 @@ public class Bookmark {
4040
@Column(name = "created_at", nullable = false)
4141
private LocalDateTime createdAt;
4242

43-
@Column(name = "place_id", nullable = false)
44-
private String placeId;
43+
@ManyToOne(fetch = FetchType.LAZY)
44+
@JoinColumn(name = "content_id", nullable = false)
45+
private OpenApiPlaceDetail contentId;
4546

4647
@Column(name = "content_type_id", nullable = false)
4748
private Integer contentTypeId;
4849

49-
public Bookmark(String userEmail, String placeId, Integer contentTypeId) {
50+
public Bookmark(String userEmail, OpenApiPlaceDetail contentId, Integer contentTypeId) {
5051
this.userEmail = userEmail;
51-
this.placeId = placeId;
52+
this.contentId = contentId;
5253
this.contentTypeId = contentTypeId;
5354
this.bookmarkType = determineBookmarkType(contentTypeId);
5455
this.createdAt = LocalDateTime.now();
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.jocketdan.tour.entity;
2+
3+
import jakarta.persistence.Column;
4+
import jakarta.persistence.Entity;
5+
import jakarta.persistence.Id;
6+
import lombok.AccessLevel;
7+
import lombok.AllArgsConstructor;
8+
import lombok.Getter;
9+
import lombok.NoArgsConstructor;
10+
import lombok.experimental.FieldDefaults;
11+
12+
@Entity
13+
@Getter
14+
@NoArgsConstructor
15+
@AllArgsConstructor
16+
@FieldDefaults(level = AccessLevel.PRIVATE)
17+
public class OpenApiPlaceDetail {
18+
19+
@Id
20+
String contentId;
21+
String title;
22+
23+
@Column(columnDefinition = "TEXT")
24+
String overview;
25+
26+
String addr1;
27+
String addr2;
28+
String zipcode;
29+
30+
Double mapx;
31+
Double mapy;
32+
33+
String contentTypeId;
34+
35+
String firstImage;
36+
String firstImage2;
37+
38+
String createdTime;
39+
String modifiedTime;
40+
41+
String cat1;
42+
String cat2;
43+
String cat3;
44+
45+
String areaCode;
46+
String sigunguCode;
47+
48+
String homepage;
49+
String tel;
50+
String telName;
51+
String cpyrhtDivCd;
52+
}

tour/src/main/java/com/jocketdan/tour/repository/BookmarkRepository.java

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,32 @@
22

33
import com.jocketdan.tour.entity.Bookmark;
44
import com.jocketdan.tour.entity.BookmarkType;
5+
import com.jocketdan.tour.entity.OpenApiPlaceDetail;
56
import org.springframework.data.domain.Page;
67
import org.springframework.data.domain.Pageable;
78
import org.springframework.data.jpa.repository.JpaRepository;
89
import org.springframework.data.jpa.repository.Query;
910
import org.springframework.data.repository.query.Param;
1011

1112
import java.util.List;
12-
import java.util.Optional;
1313

1414
public interface BookmarkRepository extends JpaRepository<Bookmark, Long> {
1515

16-
boolean existsByUserEmailAndPlaceId(String userEmail, String placeId);
16+
boolean existsByUserEmailAndContentId(String userEmail, OpenApiPlaceDetail contentId);
1717

18-
Optional<Bookmark> findByUserEmailAndPlaceId(String userEmail, String placeId);
19-
20-
List<Bookmark> findByUserEmailAndPlaceIdIn(String userEmail, List<String> placeIds);
21-
22-
Page<Bookmark> findByUserEmailAndBookmarkType(String userEmail, BookmarkType bookmarkType, Pageable pageable);
23-
24-
Page<Bookmark> findByUserEmail(String userEmail, Pageable pageable);
18+
List<Bookmark> findByUserEmailAndContentId_ContentIdIn(String userEmail, List<String> contentIds);
2519

2620
@Query("SELECT COUNT(b) FROM Bookmark b WHERE b.userEmail = :email AND b.bookmarkType = :type")
2721
long countByUserEmailAndType(@Param("email") String email, @Param("type") BookmarkType type);
2822

29-
@Query("SELECT b.placeId FROM Bookmark b WHERE b.userEmail = :email")
30-
List<String> findPlaceIdsByUserEmail(@Param("email") String email);
31-
32-
@Query("SELECT b.placeId FROM Bookmark b WHERE b.userEmail = :email AND b.bookmarkType = :type")
33-
List<String> findPlaceIdsByUserEmailAndType(@Param("email") String email, @Param("type") BookmarkType type);
23+
@Query("""
24+
SELECT b FROM Bookmark b
25+
JOIN FETCH b.contentId c
26+
WHERE b.userEmail = :userEmail
27+
AND (:type IS NULL OR b.bookmarkType = :type)
28+
""")
29+
Page<Bookmark> findByUserEmailWithContentId(
30+
@Param("userEmail") String userEmail,
31+
@Param("type") BookmarkType type,
32+
Pageable pageable);
3433
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.jocketdan.tour.repository;
2+
3+
import com.jocketdan.tour.entity.OpenApiPlaceDetail;
4+
import org.springframework.data.jpa.repository.JpaRepository;
5+
6+
import java.util.Optional;
7+
8+
public interface OpenApiPlaceDetailRepository extends JpaRepository<OpenApiPlaceDetail, String> {
9+
Optional<OpenApiPlaceDetail> findByContentId(String placeId);
10+
}

0 commit comments

Comments
 (0)