-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: 책 기능을 추가했습니다. #9
Changes from all commits
2c94978
4fd0836
cd72e13
324826b
edb8c10
0441cf6
581e923
1f25cfb
f9e1792
f8e14f9
4e20b02
b1073de
9518e59
8ed3638
c7b25a4
b1b94aa
db5b336
f6823f2
5b1a221
f935895
d656f43
1ff0c5f
a8b74f0
5ef2af1
d29e3c3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package org.poolc.api.book.client; | ||
|
||
import org.poolc.api.book.dto.response.BookApiResponse; | ||
|
||
import javax.management.modelmbean.XMLParseException; | ||
import java.util.List; | ||
|
||
public interface BookClient { | ||
|
||
List<BookApiResponse> searchBooks(String query, int page) throws XMLParseException; | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package org.poolc.api.book.client; | ||
|
||
import com.fasterxml.jackson.dataformat.xml.XmlMapper; | ||
import lombok.RequiredArgsConstructor; | ||
import org.poolc.api.book.dto.response.BookApiResponse; | ||
import org.poolc.api.book.dto.response.NaverApiResponse; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.http.HttpEntity; | ||
import org.springframework.http.HttpHeaders; | ||
import org.springframework.http.HttpMethod; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.web.client.RestTemplate; | ||
|
||
import javax.management.modelmbean.XMLParseException; | ||
import java.util.List; | ||
|
||
@Component | ||
@RequiredArgsConstructor | ||
public class NaverBookClient implements BookClient{ | ||
|
||
@Value("${book.api.url}") | ||
private String url; | ||
|
||
@Value("${book.api.secret}") | ||
private String clientSecret; | ||
|
||
@Value("${book.api.id}") | ||
private String clientId; | ||
|
||
private final RestTemplate restTemplate; | ||
|
||
private static final int PAGE_SIZE = 10; | ||
|
||
@Override | ||
public List<BookApiResponse> searchBooks(String query, int page) throws XMLParseException { | ||
HttpHeaders headers = new HttpHeaders(); | ||
headers.set("X-Naver-Client-Id", clientId); | ||
headers.set("X-Naver-Client-Secret", clientSecret); | ||
|
||
HttpEntity<String> entity = new HttpEntity<>(headers); | ||
|
||
String url = new StringBuilder(this.url) | ||
.append("?query=").append(query) | ||
.append("&display=").append(PAGE_SIZE) | ||
.append("&start=").append(page * PAGE_SIZE + 1) | ||
.toString(); | ||
|
||
String xmlResponse = restTemplate.exchange(url, HttpMethod.GET, entity, String.class).getBody(); | ||
System.out.println(xmlResponse); | ||
|
||
|
||
try { | ||
NaverApiResponse naverApiResponse = parseBooks(xmlResponse); | ||
return naverApiResponse.getChannel().getItems(); | ||
} catch (Exception e) { | ||
e.printStackTrace(); | ||
throw new XMLParseException("Failed to parse XML response"); | ||
} | ||
Comment on lines
+55
to
+58
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Avoid catching generic Catching a generic |
||
} | ||
|
||
private NaverApiResponse parseBooks(String xmlResponse) throws Exception { | ||
XmlMapper xmlMapper = new XmlMapper(); | ||
return xmlMapper.readValue(xmlResponse, NaverApiResponse.class); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,70 +1,106 @@ | ||
package org.poolc.api.book.controller; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import org.poolc.api.book.dto.BookRequest; | ||
import org.poolc.api.book.dto.BookResponse; | ||
import org.poolc.api.book.client.BookClient; | ||
import org.poolc.api.book.dto.request.CreateBookRequest; | ||
import org.poolc.api.book.dto.request.UpdateBookRequest; | ||
import org.poolc.api.book.service.BookService; | ||
import org.poolc.api.book.vo.BookCreateValues; | ||
import org.poolc.api.book.vo.BookUpdateValues; | ||
import org.poolc.api.member.domain.Member; | ||
import org.springframework.http.MediaType; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.security.core.annotation.AuthenticationPrincipal; | ||
import org.springframework.web.bind.annotation.*; | ||
|
||
import java.util.HashMap; | ||
import java.util.List; | ||
|
||
import static java.util.stream.Collectors.toList; | ||
import javax.validation.Valid; | ||
import javax.validation.constraints.Min; | ||
|
||
@RestController | ||
@RequiredArgsConstructor | ||
@RequestMapping("/book") | ||
@RequiredArgsConstructor | ||
public class BookController { | ||
|
||
private final BookClient bookClient; | ||
private final BookService bookService; | ||
|
||
@GetMapping(value = "/{bookID}", produces = MediaType.APPLICATION_JSON_VALUE) | ||
public ResponseEntity<BookResponse> findOneBookWithBorrower(@PathVariable("bookID") Long id) { | ||
return ResponseEntity.ok().body(BookResponse.of(bookService.findOneBook(id))); | ||
@GetMapping("/search") | ||
public ResponseEntity<?> searchBooks(@RequestParam String query, | ||
@RequestParam(value = "page", defaultValue = "0") @Min(0) Integer page) { | ||
try { | ||
return new ResponseEntity<>(bookClient.searchBooks(query, page), HttpStatus.OK); | ||
} catch (Exception e) { | ||
return new ResponseEntity<>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); | ||
} | ||
Comment on lines
+29
to
+32
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid returning exception messages directly in API responses Returning Also applies to: 38-41, 47-50, 57-61, 67-71, 78-82, 88-92, 98-102 |
||
} | ||
|
||
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) | ||
public ResponseEntity<HashMap<String, List<BookResponse>>> findBooks() { | ||
HashMap<String, List<BookResponse>> responseBody = new HashMap<>(); | ||
responseBody.put("data", bookService.findBooks().stream() | ||
.map(BookResponse::of) | ||
.collect(toList())); | ||
return ResponseEntity.ok().body(responseBody); | ||
@GetMapping("/all") | ||
public ResponseEntity<?> getAllBooks(@RequestParam(value = "page", defaultValue = "0") @Min(0) Integer page) { | ||
try { | ||
return new ResponseEntity<>(bookService.getAllBooks(page), HttpStatus.OK); | ||
} catch (Exception e) { | ||
return new ResponseEntity<>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); | ||
} | ||
} | ||
|
||
@PostMapping(produces = MediaType.APPLICATION_JSON_VALUE) | ||
public ResponseEntity<Void> registerBook(@RequestBody BookRequest requestBody) { | ||
bookService.saveBook(new BookCreateValues(requestBody)); | ||
return ResponseEntity.ok().build(); | ||
@GetMapping("/{id}") | ||
public ResponseEntity<?> getBook(@PathVariable Long id) { | ||
try { | ||
return new ResponseEntity<>(bookService.getBook(id), HttpStatus.OK); | ||
} catch (Exception e) { | ||
return new ResponseEntity<>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); | ||
} | ||
} | ||
|
||
@DeleteMapping(value = "/{bookID}", produces = MediaType.APPLICATION_JSON_VALUE) | ||
public ResponseEntity<Void> deleteBook(@PathVariable("bookID") Long id) { | ||
bookService.deleteBook(id); | ||
return ResponseEntity.ok().build(); | ||
@PostMapping("/new") | ||
public ResponseEntity<?> addBook(@AuthenticationPrincipal Member member, | ||
@Valid @RequestBody CreateBookRequest request) { | ||
try { | ||
bookService.createBook(member, request); | ||
return new ResponseEntity<>(HttpStatus.CREATED); | ||
} catch (Exception e) { | ||
return new ResponseEntity<>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); | ||
} | ||
} | ||
|
||
@PutMapping(value = "/{bookID}", produces = MediaType.APPLICATION_JSON_VALUE) | ||
public ResponseEntity<Void> updateBook(@RequestBody BookRequest requestBody, @PathVariable("bookID") Long id) { | ||
bookService.updateBook(id, new BookUpdateValues(requestBody)); | ||
return ResponseEntity.ok().build(); | ||
@DeleteMapping("/{id}") | ||
public ResponseEntity<?> deleteBook(@AuthenticationPrincipal Member member, @PathVariable Long id) { | ||
try { | ||
bookService.deleteBook(member, id); | ||
return new ResponseEntity<>(HttpStatus.OK); | ||
} catch (Exception e) { | ||
return new ResponseEntity<>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); | ||
} | ||
} | ||
|
||
@PutMapping(value = "/borrow/{bookID}") | ||
public ResponseEntity borrowBook(@AuthenticationPrincipal Member member, @PathVariable("bookID") Long id) { | ||
bookService.borrowBook(member, id); | ||
return ResponseEntity.ok().build(); | ||
@PutMapping("/{id}") | ||
public ResponseEntity<?> updateBook(@AuthenticationPrincipal Member member, @PathVariable Long id, | ||
@Valid @RequestBody UpdateBookRequest request) { | ||
try { | ||
bookService.updateBook(member, id, request); | ||
return new ResponseEntity<>(HttpStatus.OK); | ||
} catch (Exception e) { | ||
return new ResponseEntity<>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); | ||
} | ||
} | ||
|
||
@PutMapping(value = "/return/{bookID}") | ||
public ResponseEntity returnBook(@AuthenticationPrincipal Member member, @PathVariable("bookID") Long id) { | ||
bookService.returnBook(member, id); | ||
return ResponseEntity.ok().build(); | ||
@PostMapping("/{id}/borrow") | ||
public ResponseEntity<?> borrowBook(@AuthenticationPrincipal Member member, @PathVariable Long id) { | ||
try { | ||
bookService.borrow(member, id); | ||
return new ResponseEntity<>(HttpStatus.OK); | ||
} catch (Exception e) { | ||
return new ResponseEntity<>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); | ||
} | ||
} | ||
} | ||
|
||
@PostMapping("/{id}/return") | ||
public ResponseEntity<?> returnBook(@AuthenticationPrincipal Member member, @PathVariable Long id) { | ||
try { | ||
bookService.returnBook(member, id); | ||
return new ResponseEntity<>(HttpStatus.OK); | ||
} catch (Exception e) { | ||
return new ResponseEntity<>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); | ||
} | ||
} | ||
|
||
|
||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,11 +1,15 @@ | ||||||||||||||||||||||||||||||||||||||
package org.poolc.api.book.domain; | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | ||||||||||||||||||||||||||||||||||||||
import lombok.AllArgsConstructor; | ||||||||||||||||||||||||||||||||||||||
import lombok.Builder; | ||||||||||||||||||||||||||||||||||||||
import lombok.Getter; | ||||||||||||||||||||||||||||||||||||||
import org.poolc.api.book.dto.request.UpdateBookRequest; | ||||||||||||||||||||||||||||||||||||||
import org.poolc.api.common.domain.TimestampEntity; | ||||||||||||||||||||||||||||||||||||||
import org.poolc.api.member.domain.Member; | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
import javax.persistence.*; | ||||||||||||||||||||||||||||||||||||||
import javax.validation.constraints.Size; | ||||||||||||||||||||||||||||||||||||||
import java.time.LocalDate; | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
@Entity | ||||||||||||||||||||||||||||||||||||||
|
@@ -14,7 +18,9 @@ | |||||||||||||||||||||||||||||||||||||
name = "BOOK_SEQ_GENERATOR", | ||||||||||||||||||||||||||||||||||||||
sequenceName = "BOOK_SEQ" | ||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||
@AllArgsConstructor | ||||||||||||||||||||||||||||||||||||||
@JsonIgnoreProperties(ignoreUnknown = true) | ||||||||||||||||||||||||||||||||||||||
@Builder | ||||||||||||||||||||||||||||||||||||||
public class Book extends TimestampEntity { | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
@Id | ||||||||||||||||||||||||||||||||||||||
|
@@ -26,17 +32,32 @@ public class Book extends TimestampEntity { | |||||||||||||||||||||||||||||||||||||
@JoinColumn(name = "borrower", referencedColumnName = "UUID") | ||||||||||||||||||||||||||||||||||||||
private Member borrower = null; | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
@Column(name = "title", nullable = false, length = 1024) | ||||||||||||||||||||||||||||||||||||||
@Column(name = "title", nullable = false) | ||||||||||||||||||||||||||||||||||||||
private String title; | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
@Column(name = "author", nullable = false, length = 1024) | ||||||||||||||||||||||||||||||||||||||
private String author; | ||||||||||||||||||||||||||||||||||||||
@Column(name = "link", columnDefinition = "VARCHAR(600)") | ||||||||||||||||||||||||||||||||||||||
private String link; | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
@Column(name = "image_url", length = 1024) | ||||||||||||||||||||||||||||||||||||||
@Column(name = "image_url", columnDefinition = "VARCHAR(600)") | ||||||||||||||||||||||||||||||||||||||
private String imageURL; | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
@Column(name = "info", length = 1024) | ||||||||||||||||||||||||||||||||||||||
private String info; | ||||||||||||||||||||||||||||||||||||||
@Column(name = "author", nullable = false) | ||||||||||||||||||||||||||||||||||||||
private String author; | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
@Column(name = "description", columnDefinition = "TEXT") | ||||||||||||||||||||||||||||||||||||||
private String description; | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
@Column(name = "discount") | ||||||||||||||||||||||||||||||||||||||
private Integer discount; | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
@Column(name = "isbn") | ||||||||||||||||||||||||||||||||||||||
private String isbn; | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
@Column(name = "publisher") | ||||||||||||||||||||||||||||||||||||||
private String publisher; | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
@Column(name = "published_date") | ||||||||||||||||||||||||||||||||||||||
private String publishedDate; | ||||||||||||||||||||||||||||||||||||||
Comment on lines
+53
to
+60
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add validation for ISBN and publishedDate fields The ISBN field should validate against standard formats, and publishedDate should follow a consistent date format. - @Column(name = "isbn")
+ @Column(name = "isbn")
+ @Pattern(regexp = "^(?:ISBN(?:-1[03])?:? )?(?=[0-9X]{10}$|(?=(?:[0-9]+[- ]){3})[- 0-9X]{13}$|97[89][0-9]{10}$|(?=(?:[0-9]+[- ]){4})[- 0-9]{17}$)(?:97[89][- ]?)?[0-9]{1,5}[- ]?[0-9]+[- ]?[0-9]+[- ]?[0-9X]$")
private String isbn;
- @Column(name = "published_date")
+ @Column(name = "published_date")
+ @Pattern(regexp = "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$")
private String publishedDate; 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
@Column(name = "borrow_date") | ||||||||||||||||||||||||||||||||||||||
private LocalDate borrowDate; | ||||||||||||||||||||||||||||||||||||||
|
@@ -48,14 +69,6 @@ public class Book extends TimestampEntity { | |||||||||||||||||||||||||||||||||||||
protected Book() { | ||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
public Book(String title, String author, String imageURL, String info, BookStatus status) { | ||||||||||||||||||||||||||||||||||||||
this.title = title; | ||||||||||||||||||||||||||||||||||||||
this.author = author; | ||||||||||||||||||||||||||||||||||||||
this.imageURL = imageURL; | ||||||||||||||||||||||||||||||||||||||
this.info = info; | ||||||||||||||||||||||||||||||||||||||
this.status = status; | ||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
public void borrowBook(Member member) { | ||||||||||||||||||||||||||||||||||||||
this.status = BookStatus.UNAVAILABLE; | ||||||||||||||||||||||||||||||||||||||
this.borrowDate = LocalDate.now(); | ||||||||||||||||||||||||||||||||||||||
|
@@ -68,10 +81,16 @@ public void returnBook() { | |||||||||||||||||||||||||||||||||||||
this.borrower = null; | ||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
public void update(String title, String author, String imageURL, String info) { | ||||||||||||||||||||||||||||||||||||||
this.title = title; | ||||||||||||||||||||||||||||||||||||||
this.author = author; | ||||||||||||||||||||||||||||||||||||||
this.imageURL = imageURL; | ||||||||||||||||||||||||||||||||||||||
this.info = info; | ||||||||||||||||||||||||||||||||||||||
public void update(UpdateBookRequest request) { | ||||||||||||||||||||||||||||||||||||||
if (request.getTitle() != null) this.title = request.getTitle(); | ||||||||||||||||||||||||||||||||||||||
if (request.getLink() != null) this.link = request.getLink(); | ||||||||||||||||||||||||||||||||||||||
if (request.getImage() != null) this.imageURL = request.getImage(); | ||||||||||||||||||||||||||||||||||||||
if (request.getAuthor() != null) this.author = request.getAuthor(); | ||||||||||||||||||||||||||||||||||||||
if (request.getDescription() != null) this.description = request.getDescription(); | ||||||||||||||||||||||||||||||||||||||
if (request.getDiscount() != null) this.discount = request.getDiscount(); | ||||||||||||||||||||||||||||||||||||||
if (request.getIsbn() != null) this.isbn = request.getIsbn(); | ||||||||||||||||||||||||||||||||||||||
if (request.getPublisher() != null) this.publisher = request.getPublisher(); | ||||||||||||||||||||||||||||||||||||||
if (request.getPubdate() != null) this.publishedDate = request.getPubdate(); | ||||||||||||||||||||||||||||||||||||||
Comment on lines
+84
to
+93
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Enhance update method with validation and audit logging The current implementation should validate input values before updating and consider logging changes for audit purposes. public void update(UpdateBookRequest request) {
+ validateUpdateRequest(request);
+ logBookChanges(request);
if (request.getTitle() != null) this.title = request.getTitle();
if (request.getLink() != null) this.link = request.getLink();
if (request.getImage() != null) this.imageURL = request.getImage();
if (request.getAuthor() != null) this.author = request.getAuthor();
if (request.getDescription() != null) this.description = request.getDescription();
if (request.getDiscount() != null) this.discount = request.getDiscount();
if (request.getIsbn() != null) this.isbn = request.getIsbn();
if (request.getPublisher() != null) this.publisher = request.getPublisher();
if (request.getPubdate() != null) this.publishedDate = request.getPubdate();
}
+
+ private void validateUpdateRequest(UpdateBookRequest request) {
+ if (request.getDiscount() != null && request.getDiscount() < 0) {
+ throw new IllegalArgumentException("Discount cannot be negative");
+ }
+ // Add more validations as needed
+ }
|
||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package org.poolc.api.book.domain; | ||
|
||
public class BookBorrower { | ||
// maps book with user who borrowed it | ||
private Long bookId; | ||
private Long userId; | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package org.poolc.api.book.domain; | ||
|
||
public enum BorrowStatus { | ||
BORROWED, RETURNED, EXTENDED, OVERDUE | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
Update Jackson dependencies from 2.18.1 to 2.18.2
The current version 2.18.1 is outdated. Version 2.18.2 is available with bug fixes and improvements. Update all Jackson dependencies to maintain version consistency:
com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.18.2
com.fasterxml.jackson.core:jackson-databind:2.18.2
com.fasterxml.jackson.core:jackson-annotations:2.18.2
com.fasterxml.jackson.core:jackson-core:2.18.2
🔗 Analysis chain
Update Jackson dependencies to latest stable version
The current version 2.18.1 is not the latest stable version. Consider updating to the latest version for security patches and improvements.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
Length of output: 180