-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathBookController.kt
More file actions
98 lines (87 loc) · 5.34 KB
/
BookController.kt
File metadata and controls
98 lines (87 loc) · 5.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
package com.stepbookstep.server.domain.book.presentation
import com.stepbookstep.server.domain.book.application.BookQueryService
import com.stepbookstep.server.domain.book.presentation.dto.BookDetailResponse
import com.stepbookstep.server.domain.book.presentation.dto.BookFilterResponse
import com.stepbookstep.server.domain.book.presentation.dto.BookSearchResponse
import com.stepbookstep.server.domain.reading.domain.UserBookRepository
import com.stepbookstep.server.global.response.ApiResponse
import com.stepbookstep.server.security.jwt.LoginUserId
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.Parameter
import io.swagger.v3.oas.annotations.tags.Tag
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
@Tag(name = "Book", description = "도서 API")
@RestController
@RequestMapping("/api/v1/books")
class BookController(
private val bookQueryService: BookQueryService,
private val userBookRepository: UserBookRepository
) {
@Operation(summary = "도서 상세 조회", description = "도서 ID로 상세 정보를 조회합니다. 북마크 여부가 포함됩니다.")
@GetMapping("/{bookId}")
fun getBook(
@Parameter(description = "도서 ID") @PathVariable bookId: Long,
@Parameter(hidden = true) @LoginUserId userId: Long
): ResponseEntity<ApiResponse<BookDetailResponse>> {
val book = bookQueryService.findById(bookId)
val userBook = userBookRepository.findByUserIdAndBookId(userId, bookId)
val isBookmarked = userBook?.isBookmarked ?: false
val response = BookDetailResponse.from(book, isBookmarked = isBookmarked)
return ResponseEntity.ok(ApiResponse.ok(response))
}
@Operation(
summary = "도서 검색",
description = """
키워드로 검색하거나 사용자 등급에 맞는 도서 목록을 조회합니다.
## 키워드 입력 시
- 제목, 저자, 출판사에서 키워드를 검색합니다.
## 키워드 미입력 시
- 사용자의 독서 등급(읽는 중/완독한 도서의 평균 score)을 기반으로 레벨을 결정합니다.
- score 0~35 → level 1, score 36~65 → level 2, score 66~100 → level 3
- 해당 레벨의 도서 4권을 랜덤하게 반환합니다.
- 독서 히스토리가 없으면 level 1 도서를 반환합니다.
"""
)
@GetMapping("/search")
fun searchBooks(
@Parameter(hidden = true) @LoginUserId userId: Long,
@Parameter(description = "검색 키워드 (제목, 저자, 출판사)") @RequestParam(required = false) keyword: String?
): ResponseEntity<ApiResponse<List<BookSearchResponse>>> {
val books = bookQueryService.search(userId, keyword)
val response = books.map { BookSearchResponse.from(it) }
return ResponseEntity.ok(ApiResponse.ok(response))
}
@Operation(
summary = "도서 필터 검색",
description = """
선택한 필터 조건에 맞는 도서 목록을 조회합니다.
## 필터 옵션
- **level**: 난이도 (1, 2, 3)
- **pageRange**: 분량 (~200, 250, 350, 500, 650, 651~) - 단일 선택, 선택한 값 이하 모두 포함
- **origins**: 국가별 (한국소설, 영미소설, 중국소설, 일본소설, 프랑스소설, 독일소설) - 복수 선택 가능
- **genres**: 장르별 (로맨스, 희곡, 무협소설, 판타지/환상문학, 역사소설, 라이트노벨, 추리/미스터리, 과학소설(SF), 액션/스릴러, 호러/공포소설) - 복수 선택 가능
- **keyword**: 검색어 (제목, 저자, 출판사에서 검색) - 필터 선택 후 사용 가능
## 페이지네이션
- **cursor**: 마지막으로 조회한 bookId (첫 요청 시 생략)
- **hasNext**: 다음 페이지 존재 여부
- 정렬: id 오름차순
모든 필터는 선택 사항이며, 서로 다른 필터 간에는 AND 조건으로 검색됩니다.
동일 필터 내 복수 선택 시 OR 조건으로 검색됩니다.
keyword 입력 시 필터링된 결과 내에서 추가로 검색됩니다.
유효하지 않은 필터 값을 입력하면 400 Bad Request 에러가 반환됩니다.
"""
)
@GetMapping("/filter")
fun filterBooks(
@Parameter(description = "난이도") @RequestParam(required = false) level: Int?,
@Parameter(description = "분량 (단일 선택: ~200, 250, 350, 500, 650, 651~)") @RequestParam(required = false) pageRange: String?,
@Parameter(description = "국가별 분류 (복수 선택 가능)") @RequestParam(required = false) origins: List<String>?,
@Parameter(description = "장르별 분류 (복수 선택 가능)") @RequestParam(required = false) genres: List<String>?,
@Parameter(description = "검색어 (제목, 저자, 출판사)") @RequestParam(required = false) keyword: String?,
@Parameter(description = "마지막으로 조회한 bookId (첫 요청 시 생략)") @RequestParam(required = false) cursor: Long?
): ResponseEntity<ApiResponse<BookFilterResponse>> {
val response = bookQueryService.filter(level, pageRange, origins, genres, keyword, cursor)
return ResponseEntity.ok(ApiResponse.ok(response))
}
}